1. 程式人生 > >【探索-中級演算法】顏色分類

【探索-中級演算法】顏色分類

在這裡插入圖片描述

初級解法一

首先,使用最簡單的解法,可以拆分成兩步。

第一步:掃描陣列,先把 0 放在最前面,把 12 放在最後面(即使是混淆的也沒關係)。

第二步:再在混淆的 12 中進行排序。

每一步的排序,都需要藉助兩個指標。

public void sortColors(int[] nums) {
    if (nums == null || nums.length == 0) return;
    int p0 = -1, px = -1;
    for (int i = nums.length - 1; i >= 0; i--) {
        if (nums[i] == 0
) { p0 = i; break; } } for (int i = 0; i < nums.length; i++) { if (nums[i] != 0) { px = i; break; } } // 第一步 if (p0 != -1 && px != -1) { while (p0 > px) { swap(nums, p0, px); while
(nums[p0] != 0) --p0; while (nums[px] == 0) ++px; } } int p1 = -1, p2 = -1; for (int i = p0 + 1; i < nums.length; i++) { if (nums[i] == 2) { p2 = i; break; } } for (int i = nums.length - 1; i >= 0; i--) { if (nums[
i] == 1) { p1 = i; break; } } // 第二步 if (p1 != -1 && p2 != -1) { while (p1 > p2) { swap(nums, p1, p2); while (nums[p1] != 1) --p1; while (nums[p2] != 2) ++p2; } } } public void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; }

這樣做簡單直觀,但是效率不是很高。

初級解法二

按照題目給的提示,首先,迭代計算出 0、1 和 2 元素的個數,然後按照0、1、2的排序,重寫當前陣列。

首先,需要明確一個概念,假設 0、1 和 2 元素的個數分別為 num1、num2、num3,則在排序之後,有如下規則:

   0 所在的範圍為 [0, num1 - 1],設該範圍的小段組為 g1
   1 所在的範圍為 [num1, num1 + num 2 - 1],設該範圍的小段組為 g2
   2 所在的範圍為 [num1 + num 2, num1 - 1],設該範圍的小段組為 g3

而對於未排序的、混亂的陣列,對於 g1 內的非 0 元素都需要被交換成 0 元素,g2 的非 1 元素都需要交換成 1 元素,g3 的非 2 元素都需要交換成 2 元素,且有通常情況下,有如下交換規則(<--> 符號表示相互交換位置,g1_2 代表 g1 小組內的為 2 的元素):

g1_1 <--> g2_0,即 g1 小組範圍內的元素 1 直接與 g2 小組範圍內的元素 0 交換
g1_2 <--> g3_0
g2_2 <--> g3_1

直接把 g2、g3 中的元素 0 都交換到 g1 內,其餘組內的同理。
(需要注意,這裡說的是直接,還有一種特殊情況,不能直接交換,在下面的程式碼種有註釋說明)

如圖所示,更加直觀:
在這裡插入圖片描述

具體的演算法如下 (需要注意的是幾種特殊情況)

public static void sortColors2(int[] nums) {
    if (nums == null || nums.length == 0) return;
    int[] counts = new int[3];
    // 分別記錄各個元素的個數
    for (int i = 0; i < nums.length; i++) {
        counts[nums[i]]++;
    }
    
    // 三個指標,分別指向各個小組非小組對應的元素,如 g1 就指向 g1 小組內的元素 1 和 2
    int g1 = (counts[0] > 0 ? 0 : -1);
    int g2 = (counts[1] > 0 ? counts[0] : -1);
    int g3 = (counts[2] > 0 ? nums.length - counts[2] : -1);
    
    int f1 = g1;
    int f2 = g2;
    int f3 = g3;
    
    while (true) {
        if (g1 > -1 && g2 > -1 && nums[g1] == 1 && nums[g2] == 0) {
            swap(nums, g1, g2);
            ++g1;
            ++g2;
        } else if (g1 > -1 && g3 > -1 && nums[g1] == 2 && nums[g3] == 0) {
            swap(nums, g1, g3);
            ++g1;
            ++g3;
        } else if (g2 > -1 && g3 > -1 && nums[g2] == 2 && nums[g3] == 1) {
            swap(nums, g2, g3);
            ++g2;
            ++g3;
        } else if (g1 > -1 && g2 > -1) {
            // 特殊的處理,如對於 nums = [2, 0 ,1],
            // 此時不能直接把兩個元素交換到對應的小組內,即不屬於前面三種情況,
            // 則先交換一下當前 g1 和 g2 指向的元素
            // 這樣間接處理之後,就會使得重新符合前面三種情況之一了
            swap(nums, g1, g2);
        }
        
        // 如果元素本身就在其對應的小組內,則跳過
        while (g1 > -1 && g1 < nums.length && nums[g1] == 0) ++g1;
        while (g2 > -1 && g2 < nums.length && nums[g2] == 1) ++g2;
        while (g3 > -1 && g3 < nums.length && nums[g3] == 2) ++g3;
		
		 // 排序完成,則退出
        if ((g1 == -1 || g1 - f1 == counts[0])
                && (g2 == -1 || g2 - f2 == counts[1])
                && (g3 == -1 || g3 - f3 == counts[2])) break;
    }
}

初級解法三

其實,還可以從另外一個角度來看,因為只有 0、1、2 三種值,所以第一遍先計算每個值的個數,然後再按照每個值的數量遍歷陣列,依次設定對應下標的值即可。

進階解法

還是利用三個指標,p0、p1、p2nums(0, m-1) 內,總是分別指向排序之後的序列的最後一個 0、1、2 元素,因此,對於 mums[m],需要插入到 nums(0, m-1) 中,從而形成新的有序的 nums(0, m) 序列。

而在 nums(0, m-1) 中插入 nums[m] 的時候,假設 nums[m] == 0,則需要將原來的 p0 往後移動一位(即 ++p0),來表示新的 0 元素需要插入的位置 nums[p0],而因為新插入了元素,因為在 index = p0 之後的元素需要往後移動一位。

此時就可以使用一個技巧,直接 nums[m] == 0 覆蓋到 nums[++p0] 的位置,此時被覆蓋的元素(元素值為 1)則又轉移到 nums[++p1] 的位置(相當於轉移到 1 組成序列的末尾),而又會產生被覆蓋的元素(元素值為 2),則又轉移到 nums[++p2] 的位置(相當於轉移到 2 組成序列的末尾)。

可以看演示截圖(虛線箭頭代表指標的移動,圓圈包圍的元素代表被覆蓋的元素,弧線箭頭代表轉移):
在這裡插入圖片描述

public void sortColors(int[] nums) {
    int p0 = -1,p1 = -1,p2 = -1;
    int m = 0;
    for(; m < nums.length;m++) {
        if(nums[m] == 0){
            nums[++p2] = 2;
            nums[++p1] = 1;
            nums[++p0] = 0;
        }
        else if(nums[m] == 1){
            nums[++p2] = 2;
            nums[++p1] = 1;
        }
        else {
            ++p2;
        }
    }
}