題目描述(困難難度)
給 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 比後一個小朋友的小的情況,後一個小朋友的糖是當前小朋友的糖加 1。
1 < 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 < 3,3 的糖果已經比 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
比後一個小朋友的大,那麼理論上當前小朋友的糖要比後一個的小朋友的糖多,但此時後一個小朋友的糖還沒有確定,怎麼辦呢?
之前採用了倒着遍歷一次的方式進行了解決,這裏再考慮另外一種解法。
考慮下邊的情況。
對於第 2
個 rating 4
,它比後一個 rating
要大,所以要取決於再後邊的 rating
,一直走到 2
,也就是山底,此時對應的糖果數是 1
,然後往後走,走回山頂,糖果數一次加 1
,也就是到 rating 4
時,糖果數就是 3
了。
再一般化,山頂的糖果數就等於從左邊的山底或右邊的山底依次加 1
。
所以我們的算法只需要記錄山頂,然後再記錄下坡的高度,下坡的高度剛好是一個等差序列可以直接用公式求和。而山頂的糖果數,取決於左邊山底到山頂和右邊山底到山頂的哪個高度大。
而產生山底可以有兩種情況,一種是 rating
產生了增加,如上圖。還有一種就是 rating
不再降低,而是持平。
知道了上邊的想法,基本上就可以寫代碼了,每個人寫出來的應該都不一樣,在 discuss
區也看到了很多不同的寫法,下邊說一下我的思路。
抽象出四種情況,這裏的高度不是 rating
進行相減,而是從山底的 rating
到山頂的 rating
經過的次數。
-
左邊山底到山頂的高度大,並且右邊山底後繼續增加。
-
左邊山底到山頂的高度大,並且右邊山底是平坡。
-
右邊山底到山頂的高度大,並且右邊山底後繼續增加。
-
右邊山底到山頂的高度大,並且右邊山底是平坡。
有了這四種情況就可以寫代碼了。
我們用 total
變量記錄糖果總和, pre
變量記錄前一個小朋友的糖果數。如果當前的 rating
比前一個的 rating
大,那麼說明在走上坡,可以把前一個小朋友的糖果數加到 total
中,並且更新 pre
爲當前小朋友的糖果數。
如果當前的 rating
比前一個的 rating
小,說明開始走下坡,用 down
變量記錄連續多少次下降,此時的 pre
記錄的就是從左邊山底到山底的高度。當出現平坡或上坡的時候,將所有的下坡的糖果數利用等差公式計算。此外根據 pre
和 down
決定山頂的糖果數。
根據當前是上坡還是平坡,來更新 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 。