[LeetCode解題攻略] 41. First Missing Positive

更新於 發佈於 閱讀時間約 5 分鐘

題目描述

給定一個未排序的整數數組 nums,找出其中的最小的缺失的正整數

  • 必須設計一個時間複雜度為 O(n) 的解法,並且空間複雜度為 O(1)

範例 1

輸入: nums = [1,2,0]
輸出: 3

範例 2

輸入: nums = [3,4,-1,1]
輸出: 2

範例 3

輸入: nums = [7,8,9,11,12]
輸出: 1

解題思路

  1. 我們需要找到數組中最小的缺失正整數
  2. 題目要求時間複雜度為 O(n),這意味著傳統的排序方法(O(n log n))不適用。
  3. 同時,要求只使用常數空間,這限制了我們不能使用額外的數組來儲存結果。

基於以上限制,解法核心是將數組作為哈希表來操作,透過將元素放在正確的位置來確定缺失的數字。


解法一:原地雜湊(Cyclic Sort)

核心思想

將每個數字放到對應的索引位置,例如:

  • 如果數字為 x,且 1 <= x <= nn 是數組長度),我們應該將 x 放在索引 x-1 的位置。

最後,掃描數組,第一個數字不符合其索引的,就是缺失的正整數。


步驟

  1. 遍歷數組,將每個數字放到正確的位置:
    • 如果 nums[i] 的值在 [1, n] 範圍內,且 nums[i] != nums[nums[i] - 1],則交換 nums[i] 與 nums[nums[i] - 1]。
  2. 再次遍歷數組,找到第一個 nums[i] != i + 1 的位置,返回 i + 1
  3. 如果數組中所有數字都在正確位置,則返回 n + 1

程式碼

class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)

# 將每個數字放到正確的位置
for i in range(n):
while 1 <= nums[i] <= n and nums[i] != nums[nums[i] - 1]:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]

# 找出第一個缺失的正整數
for i in range(n):
if nums[i] != i + 1:
return i + 1

return n + 1

時間與空間複雜度分析

  • 時間複雜度:O(n)
    • 每個數字最多只會被移動一次,因此整體時間為線性。
  • 空間複雜度:O(1)
    • 我們在原地操作數組,未使用額外的儲存空間。

解法二:標記法

核心思想

  1. 利用數組本身作為狀態標記
    • 將數組中的每個數字(如果在範圍內 1 <= x <= n)標記到對應的索引位置。
    • 我們通過將數字對應位置的值設為負數來標記它已經出現過。
  2. 掃描標記結果
    • 如果某個位置的值仍為正,說明該索引的數字缺失。

步驟

  1. 遍歷數組,將 1 <= nums[i] <= n 的數字標記到對應索引。
  2. 再次遍歷數組,找到第一個值為正的索引,返回其對應數字 i + 1
  3. 如果所有位置都已標記,則返回 n + 1

程式碼

class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)

# 將不在範圍內的數字設為 n + 1
for i in range(n):
if nums[i] <= 0 or nums[i] > n:
nums[i] = n + 1

# 標記出現的數字
for i in range(n):
num = abs(nums[i])
if num <= n:
nums[num - 1] = -abs(nums[num - 1])

# 找出第一個缺失的正整數
for i in range(n):
if nums[i] > 0:
return i + 1

return n + 1

時間與空間複雜度分析

  • 時間複雜度:O(n)
    • 需要兩次遍歷數組。
  • 空間複雜度:O(1)
    • 使用數組本身作為標記,未使用額外的儲存空間。

結論

  1. 原地哈希法 是解決該問題的經典解法,特別適合實現簡單的場景。
  2. 標記法 更適合處理帶有範圍約束的問題,尤其是當需要快速排除無效數字時。

兩種解法都滿足題目對時間和空間的要求,可以根據需求選擇適合的實現方式!

歡迎來到我的部落格!這裡記錄了軟體工程師的日常生活點滴,並分享程式設計與演算法的實用教學。無論你是初學者還是有經驗的開發者,都能在這裡找到深入淺出的技術解析與實戰技巧。此外,我也會分享工作中的心路歷程與學習心得,讓你不僅學到技術,更能瞭解軟體開發的實際應用與挑戰。希望透過這個平台,能與你共同成長,激發對技術的熱情!
留言
avatar-img
留言分享你的想法!

































































給定一個可能包含重複數字的整數數組 candidates 和一個目標值 target,找出所有的唯一組合,使得這些組合中的數字和為 target。 數組中的每個數字只能在每個組合中使用一次。
給定一個無重複正整數數組 candidates 和一個目標值 target,找出所有可以使數字和為 target 的組合。 數組中的數字可以無限制重複選取,並且所有的組合需要是唯一的。
Count-and-say序列是透過遞歸公式定義的數字串序列: countAndSay(1) = "1" countAndSay(n) 是 countAndSay(n - 1) 的Run-length encoding。
編寫一個程式,求解數獨(Sudoku)問題。
給定一個 9x9 的數獨棋盤,請驗證這個棋盤是否有效。
給定一個升序排序的整數數組 nums 和一個目標值 target,請找出目標值應插入的位置,保證插入後數組仍然是有序的。 必須設計一個演算法,其時間複雜度為 O(log n)。
給定一個可能包含重複數字的整數數組 candidates 和一個目標值 target,找出所有的唯一組合,使得這些組合中的數字和為 target。 數組中的每個數字只能在每個組合中使用一次。
給定一個無重複正整數數組 candidates 和一個目標值 target,找出所有可以使數字和為 target 的組合。 數組中的數字可以無限制重複選取,並且所有的組合需要是唯一的。
Count-and-say序列是透過遞歸公式定義的數字串序列: countAndSay(1) = "1" countAndSay(n) 是 countAndSay(n - 1) 的Run-length encoding。
編寫一個程式,求解數獨(Sudoku)問題。
給定一個 9x9 的數獨棋盤,請驗證這個棋盤是否有效。
給定一個升序排序的整數數組 nums 和一個目標值 target,請找出目標值應插入的位置,保證插入後數組仍然是有序的。 必須設計一個演算法,其時間複雜度為 O(log n)。
你可能也想看
Google News 追蹤
Thumbnail
題目敘述 Patching Array 題目給定一個整數陣列, 請問還要補上多少個數字,才能用這些數字的和拼湊出所有1~n的整數。
Thumbnail
題目敘述 Single Number II 給定一個輸入陣列,已知有一個烙單的數字,其他剩餘的數字都恰巧出現三次。 請找出這個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [2,2,
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
給定一個輸入非負整樹陣列nums,請找出k值,使得陣列中恰好有k個元素大於等於 k。如果無解,回傳-1。尋找k值的方法包括排序法和二分搜尋法,時間複雜度都為O(n log n),空間複雜度為O(1)。關鍵知識點是當解空間具有遞增或遞減的性質時,可以用二分搜尋法加快搜尋效率。
Thumbnail
輸入給定一個已經從小到大排序好,而且彼此互質的整數陣列, 請問任取兩數分別當作分子、分母,第k小的分數是多少? 輸出請以 [分子,分母] 的形式回傳答案。
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定我們一個整數陣列nums,要求我們把裡面的0做元素交換,把0都搬到陣列的右邊。題目要求必須in-place在原本的陣列裡面做操作,不可以額外建立新的陣列。 題目的原文敘述 測試範例 Example 1: Input: nums = [0,1,0,3,12] Outpu
Thumbnail
題目敘述 題目會給兩個陣列nums1和nums2。 題目要求我們從中同步選擇長度為k的子序列,並且最大化子序列的分數, 回傳最高的分數值。 分數的定義: 分數 = (nums1[i0] + nums1[i1] +...+ nums1[ik - 1]) * min(nums2[i0] ,
Thumbnail
題目敘述 題目的情境是設計並且實現一個包含所有正整數的數據流,以set集合的方式存在。 數據流 = {1, 2, 3, 4, ..., ∞} 要求我們去實現定義好的function介面: SmallestInfiniteSet()建構子,初始化這個包含所有正整數的數據流。 int po
Thumbnail
題目敘述 題目會給定一個整數陣列nums,原本裡面包含有整數1到n,但是中間不小心出了差錯,導致有一個數字消失了,而另一個數字重複了。 請找出重複的數字以及消失的數字,並且 以陣列的形式[重複的數字, 消失的數字]返回這兩個數字。 例如: [1,3,3,4] 消失的數字是2,重複的數字是
Thumbnail
題目敘述 Patching Array 題目給定一個整數陣列, 請問還要補上多少個數字,才能用這些數字的和拼湊出所有1~n的整數。
Thumbnail
題目敘述 Single Number II 給定一個輸入陣列,已知有一個烙單的數字,其他剩餘的數字都恰巧出現三次。 請找出這個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [2,2,
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
給定一個輸入非負整樹陣列nums,請找出k值,使得陣列中恰好有k個元素大於等於 k。如果無解,回傳-1。尋找k值的方法包括排序法和二分搜尋法,時間複雜度都為O(n log n),空間複雜度為O(1)。關鍵知識點是當解空間具有遞增或遞減的性質時,可以用二分搜尋法加快搜尋效率。
Thumbnail
輸入給定一個已經從小到大排序好,而且彼此互質的整數陣列, 請問任取兩數分別當作分子、分母,第k小的分數是多少? 輸出請以 [分子,分母] 的形式回傳答案。
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定我們一個整數陣列nums,要求我們把裡面的0做元素交換,把0都搬到陣列的右邊。題目要求必須in-place在原本的陣列裡面做操作,不可以額外建立新的陣列。 題目的原文敘述 測試範例 Example 1: Input: nums = [0,1,0,3,12] Outpu
Thumbnail
題目敘述 題目會給兩個陣列nums1和nums2。 題目要求我們從中同步選擇長度為k的子序列,並且最大化子序列的分數, 回傳最高的分數值。 分數的定義: 分數 = (nums1[i0] + nums1[i1] +...+ nums1[ik - 1]) * min(nums2[i0] ,
Thumbnail
題目敘述 題目的情境是設計並且實現一個包含所有正整數的數據流,以set集合的方式存在。 數據流 = {1, 2, 3, 4, ..., ∞} 要求我們去實現定義好的function介面: SmallestInfiniteSet()建構子,初始化這個包含所有正整數的數據流。 int po
Thumbnail
題目敘述 題目會給定一個整數陣列nums,原本裡面包含有整數1到n,但是中間不小心出了差錯,導致有一個數字消失了,而另一個數字重複了。 請找出重複的數字以及消失的數字,並且 以陣列的形式[重複的數字, 消失的數字]返回這兩個數字。 例如: [1,3,3,4] 消失的數字是2,重複的數字是