在问 · 算法题解

135. 分发糖果

困难贪心左右两次遍历力扣原题 ↗

题目描述

n 个孩子排成一行,输入数组 ratings,ratings[i] 是第 i 个孩子的评分。

要给每个孩子发糖,规则如下:每人至少 1 颗;对任意两个相邻的孩子,评分严格更高的那个必须拿得更多。评分相同的邻居之间没有要求。

返回满足规则时最少一共要发多少颗糖。

示例 1
输入:ratings = [1,0,2]
输出:5

解释:依次发 2、1、2 颗,共 5 颗。

示例 2
输入:ratings = [1,2,2]
输出:4

解释:依次发 1、2、1 颗,最后两人评分相同,第三人只需 1 颗。

数据范围

  • 1 <= n == ratings.length <= 5 * 10^4
  • 0 <= ratings[i] <= 5 * 10^4

解题思路:左右两次遍历

把规则拆成左、右两个方向。从左往右扫:若比左邻居高,left[i] = left[i-1] + 1,否则为 1。从右往左扫:若比右邻居高,right 加 1,否则重置为 1。每个孩子取 max(left[i], right),同时满足两侧的要求,而且每一侧都是最小的必要值。

为什么正确

left[i] 是只考虑左侧规则时的最少糖数;反向扫到 i 时 right 是只考虑右侧规则时的最少糖数。

复杂度分析

时间 O(n),空间 O(n)。

易错点

评分相等时没有约束,可以只给 1 颗;不能只做一遍扫描,否则下降段的要求会被漏掉。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        left = [1] * n
        for i in range(1, n):
            if ratings[i] > ratings[i - 1]:
                left[i] = left[i - 1] + 1
        total = left[n - 1]
        right = 1
        for i in range(n - 2, -1, -1):
            if ratings[i] > ratings[i + 1]:
                right += 1
            else:
                right = 1
            total += max(left[i], right)
        return total

载入编辑器,自己跑一遍 →

其他解法

两次遍历用 O(n) 数组,思路清晰易证;数坡法把空间降到 O(1),但更难理解。

解法 2:常数空间数坡 时间 O(n),空间 O(1)

从左到右扫描,把评分序列看成上坡和下坡。上坡或持平时,当前孩子得 pre 个糖果,并用 inc 记录上坡长度。下坡时把下坡长度 dec 加 1,相当于给下坡上每个孩子多发一个。若 dec 追平 inc,坡顶也要多发一个,把坡顶并入下坡。

只用几个变量,一次遍历完成。状态含义较抽象,边界情形容易写错。

from typing import List

class Solution:
    def candy(self, ratings: List[int]) -> int:
        total, inc, dec, pre = 1, 1, 0, 1
        for i in range(1, len(ratings)):
            if ratings[i] >= ratings[i - 1]:
                dec = 0
                pre = 1 if ratings[i] == ratings[i - 1] else pre + 1
                total += pre
                inc = pre
            else:
                dec += 1
                if dec == inc:
                    dec += 1
                total += dec
                pre = 1
        return total

贪心 专题的其他题目

查看 贪心 全部题目 →