n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2] 输出:5 解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2] 输出:4 解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
解法一
根据题目,首先每个小朋友会至少有一个糖。
如果当前小朋友的 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]) {//后一个小朋友的糖果树没有前一个的多,就更新后一个等于前一个加 1if (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 等于 2if (ratings[i] > ratings[i - 1]) {pre = 2;//当前是平坡,对应情况 2 或者 4//更新 pre 等于 1} else {pre = 1;}down = 0;//之前没有出现过下坡} else {//将前一个小朋友的糖果数相加total += pre;//如果是上坡更新当前糖果数是上一个的加 1if (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)
。