135. 分发糖果
题目
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
提示:
- n == ratings.length
- 1 <= n <= 2 * 10^4
- 0 <= ratings[i] <= 2 * 10^4
贪心方法
这个问题其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼。
那么本题我们采用了两次贪心的策略:
- 一次是从左到右遍历(第一个元素左边没有元素,天然满足,所以可以从第二个元素起,从左到右进行局部贪心遍历),只比较右边孩子评分比左边大的情况。
- 一次是从右到左遍历(第n个元素右边没有元素,天然满足,所以可以从第n-1个元素起,从右到左进行局部贪心遍历),只比较左边孩子评分比右边大的情况。
这样的贪心策略,可以从局部最优推出全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。
单向遍历时,从何处起始,是使得局部最优可以退出全局最优的关键,初始处必须天然的满足一侧的限制要求,使得我们可以专注的仅关注单侧限制,否则起始处就两种限制混杂,通过左右和右左两个局部贪心是得不出全局最优的。
class Solution { public int candy(int[] ratings) { int n = ratings.length; int[] temp = new int[n]; temp[0] = 1; // 从前向后:确保右边评分大于左边时,满足放置条件 for(int i = 1; i < n; i++) { if (ratings[i] > ratings[i - 1]) { temp[i] = temp[i - 1] + 1; } else { temp[i] = 1; } } // 从后向前:确保左边评分大于右边时,满足放置条件 for(int i = n - 2; i >= 0; i--) { if(ratings[i] > ratings[i + 1]) { // temp[i]只有取最大的才能同时满足左右两种情形下的放置条件 // 想不通可以模拟下例子:[1,3,4,5,2] temp[i] = Math.max(temp[i], temp[i + 1] + 1); } } // 统计结果 int ret = 0; for (var cnt : temp) { ret += cnt; } return ret; } }
这道题如果假设孩子们首尾排成一个环,其他要求不变,则我们可以找出ratings数组中的最小值,将这个位置放置1,然后抠除这个位置的孩子,将环断开,然后按照上面的思路进行,最最后再加上最小值处放置的1即可(我们可以这样断开环,是因为这个位置评分最小,贪心时只需要放置1,无论其他位置放置何元素,天然满足关系)。
其实,上面不是环的情况,也是在首尾元素之间通过添加一个小于ratings数组所有元素的假想孩子连成一个环的特殊情况,规定这个假想孩子处必须放置0