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?
Example 1:
Input: [1,0,2]
Output: 5
Explanation: You can allocate to the first, second and third child with 2, 1, 2 candies respectively.
Example 2:
Input: [1,2,2]
Output: 4
Explanation: You can allocate to the first, second and third child with 1, 2, 1 candies respectively.
The third child gets 1 candy because it satisfies the above two conditions.
=============================2018.12.21更新============================
从左到右贪心的策略选择,注意后面下降的峰值,影响到之前那一个上升时的峰值。
class Solution {
public int candy(int[] ratings) {
int cur=1,sum=1,downpeak=1,uppeak=1;//下降时的downpeak,uppeak是上升时顶峰,cur为当前大小
for(int i=1;i<ratings.length;i++){
if(ratings[i]>ratings[i-1]){
downpeak=0;
cur++;
uppeak=cur;
sum+=cur;
}else if(ratings[i]<ratings[i-1]){
if(cur==1){
downpeak++;
sum+=downpeak;
if(downpeak==uppeak){
downpeak++;
sum++;
}
}else{
downpeak=1;
sum+=downpeak;
cur=1;
}
}else{
downpeak=1;
uppeak=1;
sum++;
cur=1;
}
}
return sum;
}
}
=================================================================
思路:观察可看出:从左到右遍历。新加入的孩子分3中情况:
1.新加入的孩子i的级别如果比左边孩子级别大,每次初始当前孩子的糖果数val[i-1]+1。这样能使当前糖果总数最少。(证明:当前点与该递增子序列第一个开始递增的点之间的点的和是最小,子序列第一个递增点之前的所有点的和又是最小的。下同)
2.新加入的孩子i的级别如果比左边孩子级别小,每次初始当前孩子的糖果数1。这样能使糖果总数最少,导致左边孩子糖果数的更新。
3.新加入的孩子i的级别如果与左边孩子相等,每次初始当前孩子的糖果数1。这样能使糖果总数最少,不会导致左边孩子糖果数的更新。
开始的想法是定义一个val[i]数组记录每个孩子的糖果数,遇到上面第二种情况,会导致之前孩子的糖果数更新,所以时间复杂度为O(n^2),空间复杂度O(n),虽然AC了,但时间太长了。
(未改进的代码)
public int candy(int[] ratings) {
int[] val=new int[ratings.length];
int sum=0;
val[0]=1;
for(int i=1;i<ratings.length;i++){
if(ratings[i]<=ratings[i-1]){
val[i]=1;
for(int j=0;i-j-1>=0;j++){//整理之前的数的值
if(ratings[i-j]<ratings[i-j-1]&&val[i-j]>=val[i-j-1]){
val[i-j-1]++;
}else{
break;
}
}
}
else{
val[i]=val[i-1]+1;
}
}
for(int i=0;i<val.length;i++){
sum+=val[i];
}
return sum;
}
后来再次观察得,其实不用维护一个数组保存每个孩子的糖果数。后面孩子糖果数的更新取决于前面某个孩子的最高峰值。只需保存,当前孩子单调递减是左边的峰值high,和当前孩子与左边某个峰值的距离dis,这两个值决定了之间孩子的糖果数的更新情况,如果dis==high的时候要更新峰值high。还需保存当前糖果和sum。
分3种情况:
1.当孩子级别递增时,根据每个孩子的糖果数比前面那个孩子的+1,更新sum,并记录最高峰值。
2.当孩子级别递减时,因为孩子每次糖果初始值为1,导致左边孩子糖果数的更新,根据左边的峰值high和dis,更新sum。
3.当孩子级别从递减,转变为递增;或孩子级别与前一个相等时。初始化峰值high,距离dis,需定义一个标记位rise和第一种情况做区分。当rise==false就是第2或第3种情况,rise==true就是第一种情况。别忘了更新sum。
时间复杂度O(n),空间复杂度O(1)。
(改进后的代码)
public int candy(int[] ratings) {
int high=1;
int dis=0;//下降时到峰值的距离
int sum=1;
boolean rise=true;//true
for(int i=1;i<ratings.length;i++){//上升
if(ratings[i]>ratings[i-1]){
if(rise){//保持上升
high++;
sum+=high;//每次更新high
}else{//前面下降碰到上升,初始化
rise=true;
high=2;
sum+=2;
dis=0;
}
}
else if(ratings[i]<ratings[i-1]){//下降
rise=false;//下降标志位
dis++;
if(dis==high){
high++;//之前最大高度增加1
sum+=dis+1;
}else{
sum+=dis;
}
}else{//当ratings[i]==ratings[i-1]时,看成 ratings[i]之后是下降状态
rise=false;
high=1;
sum+=1;
dis=0;
}
}
return sum;
}
看到网上还有两种思路,这两种贪心思想需要慢慢领会。参考了http://blog.sina.com.cn/s/blog_6dad631f0102wzhm.html
1.从权值最小的小孩开始分起,每分到一个小孩时,看他是否比身边小孩的权值大,如果大,则比身边小孩最多糖数再多1;如果小,则不变。参考http://www.cnblogs.com/shadowmydx/p/4914215.html
2.先初始化candy数组为1; 对rating数组从左到右扫描,如果遇到rating[j]=candy[j+1],则让candy[j+1]=candy[j]+1; 对rating数组从右到左扫描,如果遇到rating[j-1]>rating[j]且candy[j-1]<=candy[j],则让candy[j-1]=candy[j]+1;这样左右邻居都满足要求了。参考leetcode官方解答https://leetcode.com/articles/candy/#approach-4-single-pass-approach-with-constant-space-accepted。又看了下官方解答第四种思路和上述解法差不多。
第二种方法难以想到,需要慢慢体会。