Description
There are N children standing in a line. Each child is assigned a rating value.
You are giving candies to these children subjected to the following requirements:
- Each child must have at least one candy.
- Children with a higher rating get more candies than their neighbors.
What is the minimum candies you must give?
解題思路
注: 下文所說極小值不是數學函數定義的極小值,只是與其有相似的性質,暫且對其命名爲極小值。並且,在此處不要求極小值左右相鄰的值必須比其大,而只需其小於左側相鄰的值,小於等於右側相鄰的值。上圖中五角星所標註的位置即爲此文所定義的極小值。
主要思路是找出ratings
中所有的極小值,發給他們一顆糖,並從每一個極小值處,向左向右依次遞增糖的數量,直到rating
開始下降停止更新。
這其中有一個關鍵地方需要注意,就是 rating
相等的兩個人挨在一起,糖的數量不需要一樣多,並且應該儘可能只給一顆糖,例如:
ratings
[1, 2, 2, 3] ->candies
[1, 2, 1, 2]ratings
[2, 3, 3, 2, 1] –>candies
[1, 2, 3, 2, 1]ratings
[1, 2, 2, 2, 2, 1] –>candies
[1, 2, 1, 1, 2, 1]
詳細過程爲,對於 ratings
從頭掃到尾尋找極小值,首尾單獨寫出來。每當找到一個極小值,向左向右更新糖的數量,由於尋找極小值時是從左向右掃,所以向左更新可能會遇到被前一個極小值向右更新過的個體,這兩個方向的做法有點區別,分開來講:
向右更新
可以大致分爲三種情況:
- 一直單調遞增:糖的數量依次加一就可以;
- 先增後平再增:平即是
rating
相等,此時會有兩個拐角,第一個拐角還是按照前面糖的數量加一,其後面rating
相等的個體都只給一顆糖,第二個拐角後面繼續依次從兩顆糖開始遞增; - 先增後平再減:因爲在找到下一個極小值點時,會更新這種情況的第二個拐點,所以在此處可以與第二種情況一樣處理。
向左更新
只用考慮一種情況——單調遞增。即使遇到先增後平,也只考慮拐點那一個點,再往前的不需要修改,所以本質也是單調遞增。
向左更新的一個主要目的,就是完善前一個極小值點的漏洞,例如上圖中,紅橙交界點。
算法也很簡單,向左遍歷,不是遞增就終止;否則,判斷當前糖的數量是否小於右側糖的數量加一(遞增則從右往左依次加一),若小於則修改爲右側糖的數量加一,否則不變。
具體示例
如上圖,ratings
爲 [1, 2, 4, 3, 2, 1, 1, 2, 2, 3, 3, 3, 2, 2, 1, 3]
四個極小值點分別爲,(1, 1), (6, 1), (13, 2), (15, 1)
- (1, 1) 更新後,糖的數量分別爲 [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- (6, 1) 更新後,糖的數量分別爲 [1, 2, 4, 3, 2, 1, 1, 2, 1, 2, 1, 1, 0, 0, 0, 0]
- (13, 2) 更新後,糖的數量分別爲 [1, 2, 4, 3, 2, 1, 1, 2, 1, 2, 1, 2, 1, 1, 0, 0]
- (15, 1) 更新後,糖的數量分別爲 [1, 2, 4, 3, 2, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
最終,糖的數量分別爲 [1, 2, 4, 3, 2, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2],共計 28 顆。
Code
class Solution {
public:
int candy(vector<int>& ratings) {
int size = ratings.size();
vector<int> candies(size, 0);
if (size == 0 || size == 1) return size;
// 首部單獨判斷是否爲極小值,向右更新糖的數量
if (ratings[0] <= ratings[1]) {
candies[0] = 1;
int right = 1;
while (right < size) {
if (ratings[right] > ratings[right - 1]) {
candies[right] = candies[right - 1] + 1;
} else if (ratings[right] == ratings[right - 1]) {
candies[right] = 1;
} else {
break;
}
right++;
}
}
// 從第二個開始尋找極小值,向左右兩邊更新糖的數量
for (int pos = 1; pos < size - 1; ++pos) {
if (ratings[pos] < ratings[pos - 1] && ratings[pos] <= ratings[pos + 1]) {
candies[pos] = 1;
int left = pos - 1;
int right = pos + 1;
while (left >= 0) {
if (ratings[left] > ratings[left + 1]) {
if (candies[left + 1] + 1 > candies[left]) candies[left] = candies[left + 1] + 1;
} else {
break;
}
left--;
}
while (right < size) {
if (ratings[right] > ratings[right - 1]) {
candies[right] = candies[right - 1] + 1;
} else if (ratings[right] == ratings[right - 1]) {
candies[right] = 1;
} else {
break;
}
right++;
}
}
}
// 尾部單獨判斷是否爲極小值,向左更新糖的數量
if (ratings[size - 1] <= ratings[size - 2]) {
candies[size - 1] = 1;
int left = size - 2;
while (left >= 0) {
if (ratings[left] > ratings[left + 1] && candies[left + 1] + 1 > candies[left]) {
candies[left] = candies[left + 1] + 1;
} else {
break;
}
left--;
}
}
return accumulate(candies.begin(), candies.end(), 0);
}
};