LeetCode 力扣 135. 分發糖果

題目描述(困難難度)

給 N 個小朋友分糖,每個人至少有一顆糖。並且有一個 rating 數組,如果小朋友的 rating比它旁邊小朋友的 rating 大(不包括等於),那麼他必須要比對應小朋友的糖多。問至少需要分配多少顆糖。

- 表示糖,舉幾個例子。

1 0 2
- - -
-   -
總共就需要 5 顆糖。

1 2 2
- - -
  -
總共就需要 4 顆糖。

解法一

根據題目,首先每個小朋友會至少有一個糖。

如果當前小朋友的 rating 比後一個小朋友的小,那麼後一個小朋友的糖肯定是當前小朋友的糖加 1

比如 ration = [ 5, 6, 7] ,那麼三個小朋友的糖就依次是 1 2 3

如果當前小朋友的 rating 比後一個小朋友的大,那麼理論上當前小朋友的糖要比後一個的小朋友的糖多,但此時後一個小朋友的糖還沒有確定,怎麼辦呢?

參考 32題 的解法五,利用正着遍歷,再倒着遍歷的思想。

首先我們正着遍歷一次,只考慮當前小朋友的 rating 比後一個小朋友的小的情況。

接着再倒着遍歷依次,繼續考慮當前小朋友的 rating 比後一個小朋友的小的情況。因爲之前已經更新過一次糖果了,此時後一個小朋友的糖如果已經比當前小朋友的糖多了,就不需要進行更新了。

舉個例子

初始化每人一個糖
1 2 3 2 1 4
- - - - - -.
    
只考慮當前小朋友的 rating 比後一個小朋友的小的情況,後一個小朋友的糖是當前小朋友的糖加 11 < 2
1 2 3 2 1 4
- - - - - -
  -   

2 < 3
1 2 3 2 1 4
- - - - - -
  - -
    -

3 > 2 不考慮

2 > 1 不考慮

1 < 4
1 2 3 2 1 4
- - - - - -
  - -     -
    -
    
倒過來重新進行
繼續考慮當前小朋友的 rating 比後一個小朋友的小的情況。此時後一個小朋友的糖如果已經比當前小朋友的糖多了,就不需要進行更新。
4 1 2 3 2 1
- - - - - -
-     - -
      -
    
4 > 1 不考慮

1 < 2
4 1 2 3 2 1
- - - - - -
-   - - -
      -    
    
2 < 33 的糖果已經比 2 的多了,不需要考慮

3 > 2,不考慮

2 > 1,不考慮

所以最終的糖的數量就是上邊的 - 的和。

代碼的話,我們用一個 candies 數組保存當前的分配情況。

public int candy(int[] ratings) {
    int n = ratings.length;
    int[] candies = new int[n];
    //每人發一個糖
    for (int i = 0; i < n; i++) {
        candies[i] = 1;
    }
    //正着進行
    for (int i = 0; i < n - 1; i++) {
        //當前小朋友的 rating 比後一個小朋友的小,後一個小朋友的糖是當前小朋友的糖加 1。
        if (ratings[i] < ratings[i + 1]) {
            candies[i + 1] = candies[i] + 1;
        }
    }
    //倒着進行
    //下標順序就變成了 i i-1 i-2 i-3 ... 0
    //當前就是第 i 個,後一個就是第 i - 1 個
    for (int i = n - 1; i > 0; i--) {
        //當前小朋友的 rating 比後一個小朋友的小
        if (ratings[i] < ratings[i - 1]) {
            //後一個小朋友的糖果樹沒有前一個的多,就更新後一個等於前一個加 1
            if (candies[i - 1] <= candies[i]) {
                candies[i - 1] = candies[i] + 1;
            }

        }
    }
    //計算糖果總和
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += candies[i];
    }
    return sum;
}

時間複雜度:O(n)。

空間複雜度:O(n)。

解法二

參考 這裏

解法一中,考慮到

如果當前小朋友的 rating 比後一個小朋友的大,那麼理論上當前小朋友的糖要比後一個的小朋友的糖多,但此時後一個小朋友的糖還沒有確定,怎麼辦呢?

之前採用了倒着遍歷一次的方式進行了解決,這裏再考慮另外一種解法。

考慮下邊的情況。

對於第 2rating 4,它比後一個 rating 要大,所以要取決於再後邊的 rating,一直走到 2,也就是山底,此時對應的糖果數是 1,然後往後走,走回山頂,糖果數一次加 1,也就是到 rating 4 時,糖果數就是 3 了。

再一般化,山頂的糖果數就等於從左邊的山底或右邊的山底依次加 1

所以我們的算法只需要記錄山頂,然後再記錄下坡的高度,下坡的高度剛好是一個等差序列可以直接用公式求和。而山頂的糖果數,取決於左邊山底到山頂和右邊山底到山頂的哪個高度大。

而產生山底可以有兩種情況,一種是 rating 產生了增加,如上圖。還有一種就是 rating 不再降低,而是持平。

知道了上邊的想法,基本上就可以寫代碼了,每個人寫出來的應該都不一樣,在 discuss 區也看到了很多不同的寫法,下邊說一下我的思路。

抽象出四種情況,這裏的高度不是 rating 進行相減,而是從山底的 rating 到山頂的 rating 經過的次數。

  1. 左邊山底到山頂的高度大,並且右邊山底後繼續增加。

  2. 左邊山底到山頂的高度大,並且右邊山底是平坡。

  3. 右邊山底到山頂的高度大,並且右邊山底後繼續增加。

  4. 右邊山底到山頂的高度大,並且右邊山底是平坡。

有了這四種情況就可以寫代碼了。

我們用 total 變量記錄糖果總和, pre 變量記錄前一個小朋友的糖果數。如果當前的 rating 比前一個的 rating 大,那麼說明在走上坡,可以把前一個小朋友的糖果數加到 total 中,並且更新 pre 爲當前小朋友的糖果數。

如果當前的 rating 比前一個的 rating 小,說明開始走下坡,用 down 變量記錄連續多少次下降,此時的 pre 記錄的就是從左邊山底到山底的高度。當出現平坡或上坡的時候,將所有的下坡的糖果數利用等差公式計算。此外根據 predown 決定山頂的糖果數。

根據當前是上坡還是平坡,來更新 pre

大框架就是上邊的想法了,還有一些邊界需要考慮一下,看一下代碼。

public int candy(int[] ratings) {
    int n = ratings.length;
    int total = 0;
    int down = 0;
    int pre = 1;
    for (int i = 1; i < n; i++) {
        //當前是在上坡或者平坡
        if (ratings[i] >= ratings[i - 1]) {
            //之前出現過了下坡
            if (down > 0) {
                //山頂的糖果數大於下降的高度,對應情況 1
                //將下降的糖果數利用等差公式計算,單獨加上山頂
                if (pre > down) {
                    total += count(down);
                    total += pre;
                //山頂的糖果數小於下降的高度,對應情況 3,
                //將山頂也按照等差公式直接計算進去累加
                } else {
                    total += count(down + 1);
                }
                
                //當前是上坡,對應情況 1 或者 3
                //更新 pre 等於 2
                if (ratings[i] > ratings[i - 1]) {
                    pre = 2;
                
                //當前是平坡,對應情況 2 或者 4
                //更新 pre 等於 1
                } else {
                    pre = 1;
                }
                down = 0;
            //之前沒有出現過下坡
            } else {
                //將前一個小朋友的糖果數相加
                total += pre;
                //如果是上坡更新當前糖果數是上一個的加 1
                if (ratings[i] > ratings[i - 1]) {
                    pre = pre + 1;
                //如果是平坡,更新當前糖果數爲 1
                } else {
                    pre = 1;
                }

            }
        } else {
            down++;
        }
    }
    //判斷是否有下坡
    if (down > 0) {
        //和之前的邏輯一樣進行相加
        if (pre > down) {
            total += count(down);
            total += pre;
        } else {
            total += count(down + 1);
        }
    //將最後一個小朋友的糖果計算
    } else {
        total += pre;
    }
    return total;
}

//等差數列求和
private int count(int n) {
    return (1 + n) * n / 2;
}

這個算法相對於解法一的好處就是將空間複雜度從 O(n) 優化到了 O(1)

解法一雖然空間複雜度大一些,但是很好理解,正着遍歷,倒着遍歷的思想,每次遇到都印象深刻。解法二主要是對問題進行深入考慮,雖然麻煩些,但空間複雜度確實優化了。

更多詳細通俗題解詳見 leetcode.wang

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