题干
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入: ratings = [1,0,2]
输出: 5
解释: 你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入: ratings = [1,2,2]
输出: 4
解释: 你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
题解
先考虑最简单的情况,数组里面就三个数,要求评分比相邻孩子高的,得到的糖果也比相邻孩子多,那么如果中间的孩子评分最高,两边的孩子都比中间的孩子评分低,那么两边的孩子分得一个糖果,中间的孩子分得两个糖果,评分列表被分成了两个部分,第一个到第二个评分递增,第二个到第三个评分递减。扩展一下,无论数组中有多少个元素,都可以被分成若干个递增和递减的序列。如果当前孩子处在递增的序列中,那么分得的糖果都要比上一个孩子多一个;如果当前孩子和上一个孩子的评分相同,那么为了使得总数最少,当前孩子就只能分得一个糖果;如果当前孩子处在递减的序列中,那么这个孩子分得的糖果要比前一个孩子少一个。我们可以顺序遍历数组,并且用一个变量nums记录糖果总数,初始值为1(因为最少就1个糖果),当遍历过程中我们发现当前处于递增序列,那么就将总糖果数加上上一个孩子的糖果数再加1;当发现当前孩子和上一个孩子评分相同,那么就将总数加1;当发现现在处在递减序列,需要计算当前递减序列的长度,把总数加上这个长度。
func candy(ratings []int) int {
// nums为最终返回的糖果总数,初始值为1
// pre始终为上一个孩子的糖果数,初始值为1
// inc记录上一个非递减序列末尾的糖果数,初始为1
// dec记录当前递减序列的长度,初始为0
nums, pre, inc, dec := 1, 1, 1, 0
n := len(ratings)
for i := 1; i < n; i++ {
// 当序列递增,为了满足条件,糖果总数就是上一个孩子的糖果数+1
// 当孩子评分和上一个一样,就将当前孩子的糖果数变为1,这样总数是最小的
// 同时更新非递减序列末尾的最大糖果数
if ratings[i] >= ratings[i-1] {
if ratings[i] == ratings[i-1] {
pre = 1
} else {
pre++
}
nums += pre
inc = pre
} else {
// 当序列递减,就将总糖果数加上当前递减序列的长度+1
// 如果说递减序列的头和上一个递增序列的尾部重合了,上一个递增序列尾部的糖果数也需要+1
dec++
if dec == inc {
dec++
}
nums += dec
pre = 1
}
}
return nums
}
该方法的时间复杂度为O(n),空间复杂度为O(1)。
除了这种方法之外,还有一种更加容易理解的方法。仍然先考虑最简单的数组中有三个元素的情况,假设三个元素分别为a、b、c,且满足b > a > c,那么这三个孩子分得的糖果数应该满足b > a且b > c,我们可以先正向遍历一遍让整个序列满足第一个不等式,即满足只要孩子评分比左边的孩子评分高,那么糖果就比左边的孩子多;再倒序遍历一次满足第二个不等式,即满足只要孩子评分比右边孩子高,那么糖果就比右边孩子多。最后将两次遍历的糖果数取最大值就是该孩子应该被分得的糖果数。
func candy(ratings []int) int {
n := len(ratings)
left := make([]int, n)
for i := 0; i < n; i++ {
if i-1 >= 0 && ratings[i] > ratings[i-1] {
left[i] = left[i-1] + 1
} else {
left[i] = 1
}
}
nums := 0
right := 0
for i := n - 1; i >= 0; i-- {
if i < n-1 && ratings[i] > ratings[i+1] {
right++
} else {
right = 1
}
nums += max(left[i], right)
}
return nums
}