Title
給你一個未排序的整數數組,請你找出其中沒有出現的最小的正整數。
示例 1:
輸入: [1,2,0]
輸出: 3
示例 2:
輸入: [3,4,-1,1]
輸出: 2
示例 3:
輸入: [7,8,9,11,12]
輸出: 1
提示:
你的算法的時間複雜度應爲O(n),並且只能使用常數級別的額外空間。
哈希表
Solve
我們可以將數組所有的數放入哈希表,隨後從1開始依次枚舉正整數,並判斷其是否在哈希表中。
如果數組的長度爲N,那麼第一種做法的時間複雜度爲O(N),空間複雜度爲O(N)。
我們爲什麼要使用哈希表?
因爲哈希表是一個可以支持快速查找的數據結構:給定一個元素,可以在O(1)的時間查找該元素是否在哈希表中。
因此,我們可以考慮將給定的數組設計成哈希表的【替代產品】。
實際上,對於長度爲N的數組,其中沒有出現的最小正整數只能在[1, N+1]中,這樣以來,我們將所有在[1, N]範圍內的數放入哈希表,也可以得到最終的答案,而給定的數組恰好長度爲N,這讓我們有了一種將數組設計成哈希表的思路:
我們對數組進行遍歷,對於遍歷到的數 x,如果它在 [1, N] 的範圍內,那麼就將數組中的第 x-1 個位置(注意:數組下標從 0 開始)打上「標記」。在遍歷結束之後,如果所有的位置都被打上了標記,那麼答案是 N+1,否則答案是最小的沒有打上標記的位置加 1。
由於數組中的數沒有任何限制,因此這並不是一件容易的事情。但我們可以繼續利用上面的提到的性質:由於我們只在意 [1, N] 中的數,因此我們可以先對數組進行遍歷,把不在 [1, N] 範圍內的數修改成任意一個大於 N 的數(例如 N+1)。這樣一來,數組中的所有數就都是正數了,因此我們就可以將「標記」表示爲「負號」。算法的流程如下:
- 我們將數組中所有小於等於 0 的數修改爲 N+1;
- 我們遍歷數組中的每一個數 x,它可能已經被打了標記,因此原本對應的數爲 |x|,其中 | | 爲絕對值符號。如果 ∣x∣∈[1,N],那麼我們給數組中的第 ∣x∣−1 個位置的數添加一個負號。注意如果它已經有負號,不需要重複添加;
- 在遍歷完成之後,如果數組中的每一個數都是負數,那麼答案是 N+1,否則答案是第一個正數的位置加 1。
Code
def firstMissingPositive(self, nums: List[int]) -> int:
length = len(nums)
for i in range(length):
if nums[i] <= 0:
nums[i] = length + 1
for i in range(length):
num = abs(nums[i])
if num <= length:
nums[num - 1] = -abs(nums[num - 1])
for i in range(length):
if nums[i] > 0:
return i + 1
return length + 1
複雜度分析
時間複雜度:O(N),其中 N 是數組的長度。
空間複雜度:O(1)。
置換
Solve
將給定的數組「恢復」成下面的形式:如果數組中包含 x∈[1,N],那麼恢復後,數組的第 x - 1 個元素爲 x。
在恢復後,數組應當有[1, 2, …, N]的形式,但其中有若干個位置上的數是錯誤的,每一個錯誤的位置就代表了一個缺失的正數。
以題目中的示例二 [3, 4, -1, 1] 爲例,恢復後的數組應當爲 [1, -1, 3, 4],我們就可以知道缺失的數爲 2。
那麼我們如何將數組進行恢復呢?我們可以對數組進行一次遍歷,對於遍歷到的數x=nums[i],如果 x∈[1,N],我們就知道 x 應當出現在數組中的 x - 1 的位置,因此交換nums[i] 和 nums[x−1],這樣 x 就出現在了正確的位置。
在完成交換後,新的 nums[i] 可能還在 [1,N] 的範圍內,我們需要繼續進行交換操作,直到 x ∉ [1,N]。
注意到上面的方法可能會陷入死循環。如果 nums[i] 恰好與 nums[x−1] 相等,那麼就會無限交換下去。
因此在這種情況下,x 本就已經出現在了正確的位置,只是數組裏面有多個 x 而已。因此我們就不需要進行交換,而是開始遍歷下一個數。
由於每次的交換操作都會使得某一個數交換到正確的位置,因此交換的次數最多爲 N,整個方法的時間複雜度爲 O(N)。
Code
def firstMissingPositive(self, nums: List[int]) -> int:
length = len(nums)
for i in range(length):
while 1 <= nums[i] <= length and nums[nums[i] - 1] != nums[i]:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
for i in range(length):
if nums[i] != i + 1:
return i + 1
return length + 1
複雜度分析
時間複雜度:O(N),其中 N 是數組的長度。
空間複雜度:O(1)。