leetcode 面试经典 150 题(15/150) 135.分发糖果

68 阅读3分钟

题目描述

n 个孩子站成一排,每个孩子的评分为 ratings[i]。要求分发糖果满足:

  1. 每个孩子至少 1 颗糖果。
  2. 相邻孩子中评分更高的孩子必须获得更多糖果。

求最少需要准备的糖果总数。

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:
- 孩子0:2颗(比右侧评分高)
- 孩子1:1颗(最低)
- 孩子2:2颗(比右侧评分高)

示例 2:

输入:ratings = [1,2,2]
输出:4
解释:
- 孩子0:1颗
- 孩子1:2颗(比左侧高)
- 孩子2:1颗(与右侧评分相同)

算法思路

双向贪心遍历:通过两次遍历分别处理左右相邻关系,确保每个孩子的糖果数满足条件。

核心步骤

  1. 初始化:每个孩子至少 1 颗糖果。
  2. 左→右遍历:若当前孩子评分高于左侧,则糖果数比左侧多 1。
  3. 右→左遍历:若当前孩子评分高于右侧,则糖果数取当前值与右侧加 1 的较大者。
  4. 累加求和:所有糖果数总和即为答案。

操作解析

  • 左→右遍历:确保每个孩子比左侧评分高的糖果数正确。
  • 右→左遍历:处理左侧评分高但未被左遍历覆盖的情况,通过取较大值避免破坏已满足的条件。

复杂度分析

  • 时间复杂度:O(n),两次线性遍历。
  • 空间复杂度:O(n),存储糖果数的数组。

代码实现

func candy(ratings []int) int {
    n := len(ratings)
    if n == 0 {
        return 0
    }
    
    candies := make([]int, n)
    // 初始每人至少1颗
    for i := range candies {
        candies[i] = 1
    }
    
    // 从左到右遍历,处理右边比左边高的情况
    for i := 1; i < n; i++ {
        if ratings[i] > ratings[i-1] {
            candies[i] = candies[i-1] + 1
        }
    }
    
    // 从右到左遍历,处理左边比右边高的情况
    for i := n-2; i >= 0; i-- {
        if ratings[i] > ratings[i+1] {
            candies[i] = max(candies[i], candies[i+1] + 1)
        }
    }
    
    // 计算总糖果数
    total := 0
    for _, c := range candies {
        total += c
    }
    return total
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

关键点总结

  1. 两次遍历
    • 左遍历确保每个孩子比左侧评分高时糖果正确。
    • 右遍历修正比右侧评分高但未满足条件的情况。
  2. 取最大值操作:在右遍历时,需保持左遍历的结果不被破坏,仅当当前糖果数不足时才更新。
  3. 边界处理
    • 所有孩子初始至少 1 颗糖果。
    • 当所有孩子评分相同时,每人仅需 1 颗。

示例解析

示例1:ratings = [1,0,2]

  1. 左遍历
    • 孩子0:1
    • 孩子1:1(0 ≤ 1)
    • 孩子2:1+1=2(2 > 0) → [1,1,2]
  2. 右遍历
    • 孩子1:1(0 ≤ 2)
    • 孩子0:max(1, 1+1)=2(1 > 0) → [2,1,2]
  3. 总和:2+1+2=5。

示例2:ratings = [1,2,2]

  1. 左遍历
    • 孩子0:1
    • 孩子1:1+1=2(2 > 1)
    • 孩子2:1(2 ≤ 2) → [1,2,1]
  2. 右遍历
    • 孩子1:2(2 ≥ 1+1)
    • 孩子0:1(1 ≤ 2) → [1,2,1]
  3. 总和:1+2+1=4。