LeetCode 135. 分发糖果【困难】

108 阅读5分钟

题干

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;当发现现在处在递减序列,需要计算当前递减序列的长度,把总数加上这个长度。

算法步骤.png

以上图为例,当遍历到`2`时,发现当前处在递增序列,前一个孩子糖果数为1,因此需要将总数加2;当遍历到第二、三个`87`时,发现和前一个孩子的评分相同,为了使总数最小,我们就分一个糖果,因此将总数加1;当遍历到倒数第二个元素`2`时,发现当前处在一个递减的序列,它分得的糖果数应该要比前一个孩子少,但是前一个孩子的糖果数已经是1了,每个孩子分得的糖果数最少就是1,所以我们需要给前一个多分一个,再加上当前孩子的一个,总数加2;当遍历到最后一个元素`1`时,仍然处在递减序列,和之前的情况类似,倒数第三个孩子和倒数第二个孩子都要多分一个糖果,再加上当前孩子的一个,总数加3。不难发现,当处理递减序列的时候,要增加的糖果数就是递减序列的长度。 由此,我们可以得出如下算法
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)。
除了这种方法之外,还有一种更加容易理解的方法。仍然先考虑最简单的数组中有三个元素的情况,假设三个元素分别为abc,且满足b > a > c,那么这三个孩子分得的糖果数应该满足b > ab > 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
}