一、题目描述
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
-
每个孩子至少分配到 1 个糖果。
-
相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例1:
示例2:
二、题解
2.1、方法一
思路:贪心算法
“相邻的孩子中,评分高的孩子必须获得更多的糖果” ,也就是说相邻的孩子,评分高的孩子获得的糖果比他相邻的孩子高,同时要求最后老师发放的糖果数最少。也就是说我们必须保证当前孩子,以及他的左边孩子和右边孩子(如果有)共同获得的糖果数最少。那么我们如何去设计分发呢?如果我们同时思考两边的局部最优就会出现“顾及不暇”,无法达到全局最优。因此,我们采用先获取从孩子左边发放最优,再获取从孩子右边发放最优,然后比较两种情况下,取该孩子的最大值(以此满足条件2)。
因此,可设置两个贪心策略,分别处理,再合并:
-
从左遍历:当ratings[i-1]<ratings[i]时,i号孩子的糖果比i-1号孩子的糖果多。
-
从右遍历:当ratings[i]>ratings[i+1]时,i号孩子的糖果比i+1号孩子糖果多。
以示例1为例:
- 从左遍历:1号孩子设置为1。ratings[0]>ratings[1],因此,2号孩子设置为1;ratings[1]<ratings[2],3号还在设置为2。
- 从右遍历:3号孩子设置为1。ratings[1]<ratings[2],因此,2号孩子设置为1;ratings[0]>ratings[1],1号还在设置为2。
- 合并两个贪心处理的,1号孩子为2,2号孩子为1,3号孩子为2,总计5.
示例代码(Java):
class Solution {
public int candy(int[] ratings) {
int len = ratings.length;
int[] left = new int[len];
//从左遍历
for(int i=0;i<len;i++){
if(i>0&&ratings[i]>ratings[i-1]){
left[i]=left[i-1]+1;
}else{
left[i] = 1;
}
}
//从右遍历
int right = 0;
int candyMinSum = 0;
for(int i=len-1;i>=0;i--){
if(i<len-1&&ratings[i]>ratings[i+1]){
right ++;
}else{
right = 1;
}
candyMinSum +=Math.max(left[i],right);
}
return candyMinSum;
}
}
复杂度分析:
-
时间复杂度O(N) : 两次学生人数遍历。
-
空间复杂度O(N) : 线性辅助数组,存储left[N]个数据。
2.2、方法二
思路:常数空间/时间遍历(官方解法)
题目要求老师发放糖果总是尽量少给,且从 1 开始累计,每次要么比相邻的同学多给一个,要么重新置为 1。因此,据此规则,可得如下图:
[1、2、3、2、3、3]对应糖果为[1、2、3、1、2、1]。那么孩子的评分就只对应升序部分或者降序部分吗?如下图:
在图中,我们获知到3号孩子,即是升序部分也是降序部分。同时他比他两边孩子获得的糖果数都多。
从以上几种情况,我们可以总结出以下一些规律,(设前一个孩子得到的糖果数为pre)如下:
- 如果当前同学比上一个同学评分高,说明我们就在最近的递增序列中,直接分配给该同学 pre+1 个糖果即可。
- 否则我们就在一个递减序列中,我们直接分配给当前同学一个糖果,并把该同学所在的递减序列中所有的同学都再多分配一个糖果,以保证糖果数量还是满足条件。
- 我们无需显式地额外分配糖果,只需要记录当前的递减序列长度,即可知道需要额外分配的糖果数量。
- 同时注意当当前的递减序列长度和上一个递增序列等长时,需要把最近的递增序列的最后一个同学也并进递减序列中。
这样,我们只要记录当前递减序列的长度dec,最近的递增序列的长度inc 和前一个同学分得的糖果数量pre 即可获得最少的糖果数。
示例代码(Java):
class Solution {
public int candy(int[] ratings) {
int len = ratings.length;
int candyMinSum = 1;
int inc = 1, dec = 0, pre = 1;
for (int i = 1; i < len; i++) {
if (ratings[i] >= ratings[i - 1]) {
dec = 0;
pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1;
candyMinSum += pre;
inc = pre;
} else {
dec++;
if (dec == inc) {
dec++;
}
candyMinSum += dec;
pre = 1;
}
}
return candyMinSum;
}
}
复杂度分析:
- 时间复杂度O(n):其中 nn 是孩子的数量。我们需要遍历两次数组以分别计算满足左规则或右规则的最少糖果数量。
- 空间复杂度O(1):我们只需要常数的空间保存若干变量。