【數組&雙指針】LeetCode 75. 顏色分類【中等】

給定一個包含紅色白色藍色、共 n 個元素的數組 nums ,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色白色藍色順序排列。

我們使用整數 0、 12 分別表示紅色白色藍色

必須在不使用庫的sort函數的情況下解決這個問題。

 

示例 1:

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


示例 2:

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

提示:

n == nums.length
1 <= n <= 300
nums[i] 爲 0、1 或 2
 

進階:

你可以不使用代碼庫中的排序函數來解決這道題嗎?
你能想出一個僅使用常數空間的一趟掃描算法嗎?

【分析】

本題是經典的荷蘭國旗🇳🇱問題,根據題目提示,我們可以統計出數組中0,1,2的個數,再根據它們的數量,重寫整個數組。這種方法較爲簡單,也很容易想到。而這裏會介紹兩種基於指針進行交換的方法。

方法一:單指針 

可以考慮對數組進行兩次遍歷。在第一次遍歷中,我們將數組中所有的0交換到數組的頭部。在第二次遍歷中,我們將數組中所有的1交換到頭部的0之後。此時,所有的2都出現在數組的尾部,這樣我們就完成了排序。

具體地,我們使用一個指針ptr表示頭部的範圍,ptr中存儲了一個整數,表示數組nums從位置0到位置ptr-1都屬於頭部。ptr的初始值爲0,表示還沒有數處於頭部。

在第一次遍歷中,我們從左向右遍歷整個數組,如果找到了0,那麼就需要將0與頭部位置的元素進行交換,並將頭部向後擴充一個位置。在遍歷結束之後,所有的0都被交換到頭部的範圍,並且頭部只包含0。

在第二次遍歷中,我們從頭部開始,從左向右遍歷整個數組,如果找到了1,那麼就需要將1與頭部位置的元素進行交換,並將頭部向後擴充一個位置,在遍歷結束後,所有的1都被交換到頭部的範圍,並且都在0之後,此時所有的2都分佈在頭部之外的位置。排序完成。

時間複雜度:O(n),n爲數組nums的長度。

空間複雜度:O(1)。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        ptr = 0 # 定義指針ptr表示頭部的範圍,ptr中用於存儲一個數組,表示數組nums從位置0到位置ptr-1都屬於頭部。其初始值爲0,表示還沒有數字處於頭部之中。
        # 將數字0置於頭部。
        for i in range(n):
            if nums[i] == 0:
                nums[i], nums[ptr] = nums[ptr], nums[i]
                ptr += 1
        # 將數字1置於頭部,這個頭部範圍在0之後。
        for i in range(ptr, n):
            if nums[i] == 1:
                nums[i], nums[ptr] = nums[ptr], nums[i]
                ptr += 1

方法二 雙指針

方法一需要兩次遍歷,那麼我們是否可以僅使用一次遍歷呢?我們可以額外使用一個指針,使用兩個指針分別來交換0和1。

具體地,我們用指針p0來交換數字0,用指針p1來交換數字1,初始值都爲0,當我們從左向右遍歷整個數組時:

(1)若找到了1,那麼將其與nums[p1]進行交換,並將p1向後移動一位,這與方法一相同。

(2)如果找到了0,那麼將其與nums[p0]進行交換,並將p0向後移動一位,這樣做是正確的嗎?我們可以注意到,因爲連續的0之後是連續的1,因此如果我們將0與nums[p0]進行交換,那麼我們可能會把一個1交換出去。當p0<p1時,我們已經將一些1連續地放在頭部,此時一定會把一個1交換出去,導致答案錯誤。因此,如果p0<p1,那麼我們需要再將nums[i]與nums[p1]交換,其中i是當前遍歷到的位置,在進行了第一次交換後,nums[i]的值爲1,我們需要將這個1放到頭部的末端。在最後,無論是否有p0<p1,我們都需要將p0和p1均向後移動一個位置,而不是隻將p0向後移動一個位置。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        p0, p1 = 0, 0
        # 將數字0置於頭部。
        for i in range(n):
            if nums[i] == 1:
                nums[i], nums[p1] = nums[p1], nums[i]
                p1 += 1
            elif nums[i] == 0:
                nums[i], nums[p0] = nums[p0], nums[i]
                # 當p0 < p1,
                if p0 < p1:
                    nums[i], nums[p1] = nums[p1], nums[i]
                p0 += 1
                p1 += 1

# 複雜度分析
# 時間複雜度:O(n),其中 n 是數組nums 的長度。
# 空間複雜度:O(1)。

方法三:雙指針

與方法二類似,我們也可以考慮使用指針p0來交換數字0,使用指針p2來交換數字2。此時,p0的初始值仍然爲0,而p2的初始值爲n-1。在遍歷過程中,我們需要找出所有的0將其交換至數組頭部,並且找出所有的2將其交換至數組的尾部。

由於此時其中一個指針p2是從右向左移動的,因此當我們在從左向右遍歷整個數組時,如果遍歷到的位置超過了p2,那麼就可以直接停止遍歷了。

具體地,我們從左向右遍歷整個數組,設當前遍歷到的位置爲i,對應的元素值爲nums[i]:

(1)若找到了0,那麼與前面兩種做法類似,將其與nums[p0]進行交換,並將p0向後移動一位;

(2)若找到了2,那麼將其與nums[p2]進行交換,並將p2向前移動一個位置。

這樣做對嗎?可以發現,對於第二種情況,當我們將nums[i]與nums[p2]進行交換後,新的nums[i]可能仍然是2,也可能是0。然而此時我們已經結束了交換,開始遍歷下一個元素nums[i+1],不會再考慮nums[i]了,這樣我們就會得到錯誤的答案。

因此當我們找到2時,需要不斷地將其與nums[p2]進行交換,直到新的nums[i]不爲2。此時,如果nums[i]爲0,那麼對應着第一種情況;如果nums[i]爲1,那麼就不需要進行任何後續操作。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        n = len(nums)
        p0, p2 = 0, n - 1
        i = 0
        while i <= p2:
            while i <= p2 and nums[i] == 2:
                nums[i], nums[p2] = nums[p2], nums[i]
                p2 -= 1
            if nums[i] == 0:
                nums[i], nums[p0] = nums[p0], nums[i]
                p0 += 1
            i += 1

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章