贪心算法分配问题Candy (Hard)
题目描述
一群孩子站成一排,每一个孩子有自己的评分。现在需要给这些孩子发糖果,规则是如果一个孩子的评分比自己身旁的一个孩子要高,那么这个孩子就必须得到比身旁孩子更多的糖果;所有孩子至少要有一个糖果。求解最少需要多少个糖果。
输入输出样例
输入是一个数组,表示孩子的评分。输出是最少糖果的数量。
Input: [1,0,2]
Output: 5
在这个样例中,最少的糖果分法是 [2,1,2]。
题解
我们在求解这个问题时候首先要知道:
1.每个孩子都至少有一个糖果
2.在左右相邻的孩子中,贡献程度大的要比贡献程度小的孩子多一个糖果
3.最后把糖果加起来
这就浮现出来我们的解题思路,首先给所有的孩子发一颗糖果。
再就是解决左右相邻孩子发放糖果的问题
这里根据贡献大小的分布可分为三种情况:
1.连续增长 例:贡献度[1,2,3,4,5] 糖果数[1,2,3]
2.波动变化 例:贡献度[1,5,4,3,2] 糖果数[1,4,3,2,1]
3.连续减小 例:贡献度[5,4,3,2,1] 糖果数[5,4,3,2,1]
再根据贡献程度可以分为三种情况:
1.左>右,2.左=右,3.左<右
第一种情况:左<右,右孩子比左孩子糖果多(通过上边情况二所以这里不能写(右=左+1))
第二种情况:左=右,不变
第三种情况:左>右,左孩子比右孩子糖果多(不能写(左=右+1))
在这里只需考虑“左>右”和“左<右”的情况,因为“左=右”时不发生任何变化。所以就用遍历的思想第一遍从左到右遍历“左>右”的情况,再从右到左遍历“左<右”的情况。
int candy(vector<int>& ratings) {
int size = ratings.size();
if (size < 2) { //注意一个孩子的情况一个孩子就一个糖果
return size;
}
vector<int> num(size, 1); //为每个孩子分发一个糖果
for (int i = 1; i < size; ++i) {
if (ratings[i] > ratings[i-1]) {
num[i] = num[i-1] + 1; //第一遍遍历不用考虑波动变化的情况因为每个孩子才发了一个糖果
}
}
for (int i = size - 1; i > 0; --i) {
if (ratings[i] < ratings[i-1]) {
num[i-1] = max(num[i-1], num[i] + 1);//第二遍遍历需要考虑波动变化的情况
}
}
return accumulate(num.begin(), num.end(), 0); // std::accumulate 可以很方便 地求和 }
补充:有上面两个分类的前提再深入思考一下,波动变化中的贡献度增减趋势变化处(糖果跟着贡献度的变化而变化),也可以说断点处又可分为几类(安从左到右遍历再从右到左遍历的先后顺序)
总:[a,d] //下面假设拆分成两块
1.[a,b]:增加 ,[c,d]:增加 只在b,c处出现“左>右”的情况(注意这里b>c)
2.[a,b]:递减 ,[c,d]:递减 只在b,c处出现“左<右”的情况(注意这里b<c)
3.[a,b]:递增 ,[c,d]:递减 (注意这里b>c)
4.[a,b]:递减 ,[c,d]:递增 (注意这里b>c)
第一种情况
从左到右遍历:如果左<右,右=左+1。b的糖果>=1,c的糖果=1(若a,b只有一个元素b的糖果为1)。
从右到左遍历在b,c处:若b的糖果=1,则左=右+1,若b的糖果>1,则左糖果不变。从这里可以归纳出取num[i-1] = max(num[i-1], num[i] + 1)。
第二种情况
从左到右遍历:只在b,c处出现“左<右”的情况,右=左+1,从右到左遍历:出现“左>右”,左=右+1
第三种情况
从左到右遍历:只需注意b,c处,b,c处的糖果数不一定谁大,他们的大小取决于递增递减的次数。
从右到左遍历到b,c时,以为b>c,所以取num[i-1] = max(num[i-1], num[i] + 1)。
第四种情况
从左到右遍历:如果左<右,右=左+1。
从右到左遍历:b,c处b>c。且c处的糖果数为1,则从b开始依次加1
需要注意的是这个算法在input:[1,2,2,2,2,1]时output:[1,2,1,1,2,1]要想输出[1,2,2,2,2,1]需再设定一个num[i-1]==num[i]的条件
这些都是作者自己一个字一个字码的,希望对你有所帮助,如果觉得对你有帮助,请给作者点个赞👍