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);
}
};