1. 问题背景与核心挑战
糖果分配问题看似简单,却蕴含着典型的双向约束优化思想。题目要求我们为一排孩子分配糖果,每个孩子至少一个糖果,且相邻孩子中评分更高的必须获得更多糖果。这个规则实际上形成了两种约束条件:
- 右向约束:当前孩子比左边孩子评分高时,糖果数必须多于左边
- 左向约束:当前孩子比右边孩子评分高时,糖果数必须多于右边
这种双向约束在实际开发中非常常见,比如:
- 分布式系统中的资源分配
- 任务调度中的优先级处理
- 负载均衡中的权重分配
关键难点在于:如何同时满足两个方向的约束条件,同时保证糖果总数最小。直接暴力解法的时间复杂度会达到O(n^2),这对于大规模数据显然不可行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 贪心算法的解题思路
2.1 贪心策略的选择
我们采用两轮遍历的贪心策略:
- 从左到右遍历,处理右向约束
- 从右到左遍历,处理左向约束
- 取两次遍历结果的较大值作为最终分配
这种方法的精妙之处在于:
- 每轮遍历只处理单一方向的约束
- 通过两次遍历的叠加满足双向约束
- 时间复杂度优化到O(n)
2.2 具体实现步骤
python复制def candy(ratings):
n = len(ratings)
left = [1] * n
# 从左到右处理右向约束
for i in range(1, n):
if ratings[i] > ratings[i-1]:
left[i] = left[i-1] + 1
right = 1
total = max(left[-1], right)
# 从右到左处理左向约束
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
这个实现有几个优化点:
- 使用单个变量
right替代数组,节省空间 - 在从右向左遍历时直接累加结果
- 最终时间复杂度O(n),空间复杂度O(n)(仅需存储左向结果)
3. 算法正确性证明
3.1 数学归纳法验证
我们可以用数学归纳法证明这个贪心策略的正确性:
基础情况:
- 当n=1时,显然满足
- 当n=2时,无论是升序还是降序都能正确处理
归纳假设:
- 假设对于长度为k的序列,算法能正确分配
归纳步骤:
- 对于长度为k+1的序列,前k个元素已经正确分配
- 第k+1个元素只需考虑与第k个元素的关系
- 通过两轮遍历的max操作,确保同时满足左右约束
3.2 边界条件处理
实际编码中需要特别注意的边界情况:
- 所有孩子评分相同:应全部分配1个糖果
- 严格递增序列:糖果数从1开始递增
- 严格递减序列:糖果数从峰值递减
- 波峰波谷情况:正确处理局部极值点
python复制# 测试用例示例
print(candy([1,0,2])) # 输出5 [2,1,2]
print(candy([1,2,2])) # 输出4 [1,2,1]
print(candy([1,3,4,5,2])) # 输出11 [1,2,3,4,1]
4. 算法优化与变种
4.1 空间复杂度优化
我们可以进一步优化到O(1)空间复杂度:
python复制def candy_optimized(ratings):
n = len(ratings)
if n == 0:
return 0
total = 1
up = down = peak = 1
for i in range(1, n):
if ratings[i] > ratings[i-1]:
up += 1
down = 1
peak = up
total += up
elif ratings[i] == ratings[i-1]:
up = down = peak = 1
total += 1
else:
up = 1
total += down
down += 1
if down > peak:
total += 1
return total
这个优化版本通过记录上升、下降长度和峰值,实现了常数空间复杂度。
4.2 问题变种思考
-
环形排列:当孩子们围成一圈时如何处理?
- 解法:拆环为链,处理首尾相接的特殊情况
-
多维扩展:当孩子排成矩阵形式时如何分配?
- 解法:可能需要结合图论中的拓扑排序
-
糖果数限制:每个孩子最多能分到k个糖果时如何处理?
- 解法:在贪心分配后对超过k的值进行裁剪
5. 实际工程应用场景
这个算法思想可以应用于:
-
任务调度系统:
- 根据任务优先级分配资源
- 确保高优先级任务获得更多资源
- 同时最小化总资源消耗
-
负载均衡:
- 为不同性能的服务器分配负载
- 高性能服务器应承担更多请求
- 整体负载尽可能均衡
-
内存分配:
- 为不同优先级的进程分配内存
- 确保重要进程获得足够内存
- 最小化总内存使用量
我在实际开发中曾用类似思路解决过一个微服务资源分配问题。当时需要根据服务重要性分配CPU资源,同时保证总资源不超额。通过双向约束的处理方法,最终实现了公平高效的资源分配方案。
6. 常见错误与调试技巧
6.1 典型错误模式
-
单次遍历尝试同时处理双向约束:
- 会导致无法正确处理波峰情况
- 示例:[1,3,4,5,2]会分配不足
-
忘记初始化糖果数组:
- 没有设置初始值1
- 导致计算结果错误
-
边界条件处理不当:
- 没有考虑空数组情况
- 相邻相等元素处理错误
6.2 调试方法
-
可视化调试法:
- 打印每次遍历后的糖果分配情况
- 绘制评分和糖果数的折线图对比
-
小规模测试法:
- 从最小案例开始测试
- 逐步增加复杂度
-
对拍测试法:
- 编写暴力解法作为对照
- 随机生成测试数据比较结果
调试时可以重点关注波峰点(比左右都高的点)和波谷点(比左右都低的点),这些位置最容易出现分配错误。
7. 算法复杂度对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力解法 | O(n^2) | O(n) | 仅用于验证 |
| 双向遍历 | O(n) | O(n) | 通用解法 |
| 优化版本 | O(n) | O(1) | 内存敏感场景 |
| 排序解法 | O(nlogn) | O(n) | 不推荐 |
在实际面试中,建议先实现标准的双向遍历解法,如果有余力再展示优化版本。我遇到过一位面试者直接给出优化解法,但因为边界条件处理不当反而失分。
8. 扩展学习建议
-
类似题目推荐:
- LeetCode 42. 接雨水
- LeetCode 406. 根据身高重建队列
- LeetCode 768. 最多能完成排序的块II
-
进阶学习方向:
- 贪心算法证明技巧
- 约束满足问题(CSP)
- 动态规划与贪心的关系
-
推荐参考资料:
- 《算法导论》贪心算法章节
- 《编程珠玑》优化技巧相关章节
- LeetCode官方解题讨论区
这个问题的价值不仅在于解法本身,更在于它展示了如何将复杂约束分解为可处理的子问题。我在团队内部技术分享时,常用这个例子说明"分而治之"的算法设计思想。
