每日題。cycle sort 系列。 總覺得這東西很雞肋,姑且記錄一下。

原版 cycle sort 時間複雜度 O(N^2),優點是寫入次數比較少,感覺有點雞肋。
本系列用的都是閹割版本,不能正確排序重複數字,也因此時間是複雜度比較低的 O(N)。
從 [1, N] 的連續數字中找缺失/重複的數字題型,大部分都可以使用閹割版。

題目

輸入未排序的整數陣列 nums。
回傳在 nums 中沒有出現最小正整數

時間複雜度必須是 O(N),且只使用 O(1) 額外空間。

解法

陣列長度為 N,最差情況下 [1, N] 各出現一次。 而陣列索引為 [0, N-1],如果把數字 x 放到索引 x - 1 的位置,正好一人一格。
如果要放的目標位置已經被占用,那就代表出現重複。但我們只管缺失,不管重複,就直接把他留著,反正之後會被擠到後面。

舉個簡單例子:

nums = [1,1,2]
i = 0,nums[0] = 1
1 正好要放在 nums[0],不動作
i = 1,nums[1] = 1
1 應該放 nums[0]
但是 nums[0] 已經是 1,出現重複,放著不管
這時候一樣 nums = [1,1,2]
i = 2,nums[2] = 2
2 應該放 nums[1]
nums[1] = 1,沒被占用,所以把 nums[1] 和 nums[2] 交換
最後 nums = [1,2,1]

回到剛才講的 nums[i] 應該要放 i + 1。
從頭遍歷 nums 看哪個位置上的數字不對,代表他沒出現。
如果 [1, N] 都出現了,那答案很明顯只能是 N + 1。


但是 nums 中會出現 [1, N] 以外的數,如負數甚至N + k
這些東西絕對不可能找到合理的排放位置,碰到的話直接不管,之後同樣會被擠到後面。

這兩層迴圈乍看之下很像是 O(N^2),其實不然。
對於滿足 1 <= x <= N 的元素 x,每次把 x 換位都會換到正確且空閒位置上,而陣列中只有 N 個空位,因此最多只會換位 N 次。

時間複雜度 O(N)。
空間複雜度 O(1)。

class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        N = len(nums)
        for i in range(N):
            while nums[i] != i + 1:
                # ignore invalid
                if not (1 <= nums[i] <= N):
                    break
                    
                j = nums[i] - 1
                # ignore dup
                if nums[j] == nums[i]: 
                    break
                
                # swap nums[i] to nums[i - 1]
                nums[i], nums[j] = nums[j], nums[i]
        
        for i in range(N):
            if nums[i] != i + 1:
                return i + 1
        
        return N + 1

再來說說我最初想到的解法。

如果可以用額外空間,大概很多人都會選擇用 set 來標記出現過的元素。
礙於 O(1) 額外空間的限制,可以靠修改輸入參數來乘載額外的訊息
nums[i] 除了代表出現的數字以外,還可以用負數來表示 i + 1 這個數有出現過。
因為負數的影響,abs(nums[i]) 才代表真正存在的數。

細心的同學馬上就想問:啊如果 nums[i] 本來就是負數或是零怎辦?
記得本題中負數都是不要的,在開始標記之前,先把他們改成一個超大正數就好。

時間複雜度 O(N)。
空間複雜度 O(1)。

class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        N = len(nums)
        # ignore invalid
        for i in range(N):
            if nums[i] <= 0:
                nums[i] = inf
                
        # mark seen
        for i in range(N):
            x = abs(nums[i])
            if 1 <= x <= N:
                j = x - 1
                nums[j] = -abs(nums[j])
                
        for i in range(N):
            if nums[i] >= 0:
                return i + 1
            
        return N + 1