1. 分糖果问题概述
分糖果问题是一个经典的数组操作题目,通常描述为:给定一个数组代表每个孩子的评分,要求按照以下规则分配糖果:
- 每个孩子至少分到一颗糖果
- 评分更高的孩子必须比相邻孩子获得更多糖果
这个问题看似简单,但实际涉及数组遍历、贪心算法和动态规划等多个编程概念。我在实际面试中多次遇到这个问题的变种,也曾在LeetCode上看到过它的各种变形题目。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题分析与解法思路
2.1 基础解法:两次遍历法
最直观的解法是进行两次遍历:
- 从左到右遍历,确保右边评分高的孩子比左边多
- 从右到左遍历,确保左边评分高的孩子比右边多
python复制def candy(ratings):
n = len(ratings)
candies = [1] * n
# 从左到右遍历
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
# 从右到左遍历
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
return sum(candies)
注意:第二次遍历时需要取max值,因为要同时满足两个方向的规则。
2.2 时间复杂度优化
这个解法的时间复杂度是O(n),空间复杂度也是O(n)。在实际应用中,如果评分数组很大,可以考虑空间优化:
python复制def candy(ratings):
n = len(ratings)
if n == 0: return 0
total = 1
up = 1
down = 0
peak = 1
for i in range(1, n):
if ratings[i] > ratings[i-1]:
up += 1
peak = up
down = 0
total += up
elif ratings[i] == ratings[i-1]:
up = 1
down = 0
peak = 1
total += 1
else:
down += 1
up = 1
total += down
if down >= peak:
total += 1
return total
这个优化版本将空间复杂度降到了O(1),但理解起来稍微复杂一些。
3. 常见变种与扩展问题
3.1 环形分糖果问题
当孩子们围成一个圈时,问题会变得更加复杂。这种情况下,我们需要考虑首尾相连的情况:
python复制def circular_candy(ratings):
n = len(ratings)
if n == 0: return 0
if n == 1: return 1
candies = [1] * n
# 处理环形情况
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
# 处理首尾相连
if ratings[0] > ratings[-1]:
candies[0] = max(candies[0], candies[-1] + 1)
# 反向遍历
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
# 再次检查首尾
if ratings[-1] > ratings[0]:
candies[-1] = max(candies[-1], candies[0] + 1)
return sum(candies)
3.2 最小糖果总数限制
有时候题目会要求找出满足条件的最小糖果总数。这种情况下,我们需要在保证规则的前提下尽量减少糖果分配:
python复制def min_candy(ratings):
n = len(ratings)
if n == 0: return 0
candies = [1] * n
# 找出所有局部最小值
for i in range(1, n-1):
if ratings[i] <= ratings[i-1] and ratings[i] <= ratings[i+1]:
candies[i] = 1
# 从局部最小值向两边扩展
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
return sum(candies)
4. 实际应用中的注意事项
4.1 边界条件处理
在实际编码中,有几个边界条件需要特别注意:
- 空数组情况
- 所有评分相同的情况
- 严格递增或严格递减的情况
python复制# 边界条件测试用例
test_cases = [
([], 0), # 空数组
([1], 1), # 单个元素
([1,1,1], 3), # 所有评分相同
([1,2,3,4,5], 15), # 严格递增
([5,4,3,2,1], 15), # 严格递减
([1,3,4,5,2], 11) # 一般情况
]
4.2 性能优化技巧
对于大规模数据,可以考虑以下优化:
- 使用numpy数组代替Python列表
- 并行处理左右遍历
- 提前终止条件判断
python复制import numpy as np
def candy_numpy(ratings):
ratings = np.array(ratings)
n = len(ratings)
if n == 0: return 0
candies = np.ones(n, dtype=int)
# 正向遍历
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
# 反向遍历
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
return candies.sum()
5. 常见错误与调试技巧
5.1 典型错误模式
- 只进行一次遍历,忽略反向规则
- 在反向遍历时没有取max值
- 没有正确处理相等的评分情况
python复制# 错误示例1:只进行单向遍历
def wrong_candy1(ratings):
candies = [1] * len(ratings)
for i in range(1, len(ratings)):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
return sum(candies) # 会失败于[1,3,2]这样的用例
# 错误示例2:反向遍历时没取max
def wrong_candy2(ratings):
candies = [1] * len(ratings)
for i in range(1, len(ratings)):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
for i in range(len(ratings)-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = candies[i+1] + 1 # 应该取max
return sum(candies) # 会失败于[1,2,3,1]这样的用例
5.2 调试技巧
- 打印中间结果:在两次遍历后打印candies数组
- 使用小规模测试用例手动验证
- 绘制评分和糖果分配的折线图对比
python复制def debug_candy(ratings):
n = len(ratings)
candies = [1] * n
print("初始:", candies)
# 正向遍历
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
print("正向后:", candies)
# 反向遍历
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
print("反向后:", candies)
return sum(candies)
6. 进阶思考与扩展
6.1 多维度评分问题
如果评分不止一个维度(比如同时考虑成绩和纪律),问题会变得更加复杂。这种情况下可以考虑:
- 先按一个维度排序
- 再按另一个维度调整
- 可能需要使用更复杂的动态规划方法
python复制def multi_dim_candy(scores, disciplines):
# 先按成绩分配
candies = [1] * len(scores)
for i in range(1, len(scores)):
if scores[i] > scores[i-1]:
candies[i] = candies[i-1] + 1
# 再按纪律调整
for i in range(len(scores)-2, -1, -1):
if disciplines[i] > disciplines[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
return sum(candies)
6.2 糖果种类限制
如果糖果种类有限制(比如只有3种糖果),问题就变成了图着色问题。这种情况下:
- 相邻的孩子不能有相同糖果
- 评分高的孩子糖果种类值更大
python复制def limited_candy(ratings, k=3):
n = len(ratings)
if n == 0: return 0
candies = [1] * n
# 分配基本糖果
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
elif ratings[i] == ratings[i-1]:
candies[i] = 1
# 调整不超过k
for i in range(n):
if candies[i] > k:
# 需要重新调整前面的分配
j = i
while j >= 0 and candies[j] > k:
candies[j] = k
if j > 0 and ratings[j-1] > ratings[j] and candies[j-1] <= candies[j]:
candies[j-1] = candies[j] + 1
j -= 1
return sum(candies)
在实际编码面试中,分糖果问题往往不是考察最终解法,而是考察解题思路和编码能力。我建议先从简单解法开始,再逐步优化,同时注意代码的可读性和边界条件的处理。
