1. 算法训练营第二十七天核心题目解析
今天我们要啃下三块硬骨头:LeetCode455.分发饼干、376.摆动序列和53.最大子数组和。这三道题看似类型各异,实则都暗藏贪心算法的精髓。作为代码随想录训练营的经典题目组合,它们能帮助我们建立解决实际问题的思维框架。
提示:建议按题目顺序练习,从简单的分配问题过渡到复杂的序列处理,最后攻克动态规划与贪心的结合题型。
1.1 LeetCode455. 分发饼干
这是典型的"双指针+贪心"入门题。题目要求将尺寸不同的饼干分配给胃口值不同的孩子,目标是让尽可能多的孩子满足(即饼干尺寸≥胃口值)。最优策略是:
- 将孩子胃口数组g和饼干尺寸数组s分别排序
- 用两个指针i,j分别遍历两个数组
- 当s[j]≥g[i]时,计数+1,两个指针都右移
- 否则只移动饼干指针j
python复制def findContentChildren(g, s):
g.sort()
s.sort()
i = j = count = 0
while i < len(g) and j < len(s):
if s[j] >= g[i]:
count += 1
i += 1
j += 1
return count
避坑指南:
- 未排序直接匹配会导致错过最优解
- 指针移动条件判断错误会造成重复分配
- 时间复杂度O(nlogn)来自排序步骤,实际匹配过程是O(n)
1.2 LeetCode376. 摆动序列
这道题要求找出最长摆动子序列的长度,即连续数字之间的差值正负交替。例如[1,7,4,9,2,5]的摆动序列长度为6。
关键思路是用两个变量记录上升和下降的趋势:
- up:以当前元素为上升结尾的最长摆动序列长度
- down:以当前元素为下降结尾的最长摆动序列长度
python复制def wiggleMaxLength(nums):
if len(nums) < 2:
return len(nums)
up = down = 1
for i in range(1, len(nums)):
if nums[i] > nums[i-1]:
up = down + 1
elif nums[i] < nums[i-1]:
down = up + 1
return max(up, down)
常见错误:
- 忽略连续相等元素的情况
- 错误初始化up/down值
- 未考虑单元素或空数组的边界情况
1.3 LeetCode53. 最大子数组和
这道经典题目可以用贪心或动态规划解决。贪心策略是当连续和变为负数时立即重置,因为负数只会拖累后续求和。
python复制def maxSubArray(nums):
max_sum = current_sum = nums[0]
for num in nums[1:]:
current_sum = max(num, current_sum + num)
max_sum = max(max_sum, current_sum)
return max_sum
优化技巧:
- 使用Kadane算法可将复杂度降至O(n)
- 记录起始和结束下标可扩展题目要求
- 分治法也能解决但代码更复杂
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 贪心算法核心思想深度剖析
贪心算法在解决这些问题时展现出独特的优势,但也存在局限性。理解其适用场景至关重要。
2.1 贪心算法的三大特征
- 局部最优选择:每个步骤都做出当前看来最佳的选择
- 无后效性:当前选择不会影响后续子问题的结构
- 最优子结构:问题的最优解包含子问题的最优解
2.2 适用场景判断方法
当问题满足以下条件时,可考虑贪心算法:
- 可以通过局部最优推导全局最优
- 不需要考虑所有可能的解
- 问题可以分解为相互独立的子问题
注意:贪心算法不一定总能得到全局最优解,必须证明其正确性。例如背包问题就不能用简单贪心解决。
3. 题目变种与进阶训练
掌握基础解法后,可以尝试这些变种题目来巩固知识:
3.1 分发饼干变种
- 每个孩子可以拿多块饼干(累计尺寸≥胃口)
- 饼干有不同类型限制(如过敏原)
- 引入饼干成本最小化问题
3.2 摆动序列扩展
- 计算所有摆动子序列的数量
- 找出具体的摆动子序列而不仅是长度
- 三维摆动序列(股票交易中加入成交量维度)
3.3 最大子数组和进阶
- 环形数组中的最大子数组和
- 二维矩阵中的最大子矩阵和
- 允许最多跳过k个元素的最大子数组和
4. 刷题策略与效率提升
根据代码随想录的训练营经验,我总结出几点高效刷题建议:
- 分类突破:按算法类型集中练习,比如连续3天专攻贪心题目
- 三遍法则:第一遍看思路,第二遍自己写,第三遍优化代码
- 错题本制度:记录每个错题的犯错点和正确思路
- 计时训练:模拟面试环境,控制每题15-20分钟
对于这三道题,建议的练习顺序和时间分配:
- 分发饼干(30分钟):理解基础贪心思想
- 摆动序列(45分钟):掌握状态转换技巧
- 最大子数组和(60分钟):综合运用贪心和动态规划
5. 调试技巧与常见问题
在实现这些算法时,有几个调试技巧特别有用:
5.1 可视化追踪法
对于摆动序列问题,可以画出数字折线图,直观观察摆动点:
code复制数字序列: [1,17,5,10,13,15,10,5,16,8]
变化趋势: ↗ ↘ ↗ ↗ ↗ ↘ ↘ ↗ ↘
摆动点: ↑ ↑ ↑ ↑ ↑
5.2 边界测试用例
这些边界用例能有效检验代码鲁棒性:
- 空数组输入
- 单元素数组
- 全相同元素数组
- 完全递增/递减序列
5.3 变量监控表
对于最大子数组和,可以制作变量追踪表:
| 当前数字 | current_sum | max_sum |
|---|---|---|
| -2 | -2 | -2 |
| 1 | 1 | 1 |
| -3 | -2 | 1 |
| 4 | 4 | 4 |
| -1 | 3 | 4 |
| 2 | 5 | 5 |
| 1 | 6 | 6 |
| -5 | 1 | 6 |
| 4 | 5 | 6 |
6. 性能优化与复杂度分析
深入理解这些算法的时间空间复杂度对面试至关重要:
6.1 分发饼干
- 时间复杂度:O(nlogn) 主要来自排序
- 空间复杂度:O(1) 只用了常数空间
- 优化空间:如果输入已排序可降至O(n)
6.2 摆动序列
- 时间复杂度:O(n) 单次遍历数组
- 空间复杂度:O(1) 只存储两个状态变量
- 替代方案:用差分数组可以更直观但空间O(n)
6.3 最大子数组和
- 时间复杂度:O(n) 完美单次遍历
- 空间复杂度:O(1) 仅需几个变量
- 分治法:O(nlogn) 时间但代码更复杂
7. 面试实战技巧
在技术面试中遇到这类题目时,建议采用以下应对策略:
- 明确问题:先确认理解题意,举例说明
- 暴力法:先提出简单解决方案,分析复杂度
- 优化思路:讨论如何改进,引入贪心思想
- 代码实现:写出清晰可读的代码
- 测试验证:用多种用例测试代码正确性
对于最大子数组和,一个常见的follow-up问题是:"如何修改代码来返回最大子数组的起止下标?" 解决方案是增加两个指针跟踪位置:
python复制def maxSubArrayWithIndex(nums):
max_sum = current_sum = nums[0]
start = end = 0
temp_start = 0
for i in range(1, len(nums)):
if nums[i] > current_sum + nums[i]:
current_sum = nums[i]
temp_start = i
else:
current_sum += nums[i]
if current_sum > max_sum:
max_sum = current_sum
start = temp_start
end = i
return max_sum, start, end
8. 学习资源推荐
为了更系统性地掌握贪心算法,这些资源值得参考:
- 代码随想录贪心算法专题:系统整理各类贪心题型
- 《算法导论》:第十六章详细讲解贪心算法原理
- LeetCode探索卡片:交互式学习贪心算法基础
- MIT OpenCourseWare:6.006课程中的贪心算法讲座
对于想参加算法竞赛的同学,建议额外练习:
- Codeforces上的贪心标签题目
- AtCoder Beginner Contest中的C/D题
- 谷歌Kick Start轮次中的贪心问题
9. 个人刷题心得
在代码随想录训练营中完成这组题目后,我总结了几个关键收获:
- 贪心选择性质:要大胆假设局部最优能导致全局最优,但必须验证
- 排序预处理:很多贪心问题都需要先排序,如分发饼干
- 状态记录:像摆动序列这类问题,需要巧妙设计状态变量
- 算法融合:最大子数组和展示了贪心与DP的结合可能性
一个实用的练习方法是:每解决一道题后,尝试自己出2-3个变种题目。例如在分发饼干问题上,可以设想:
- 如果每个孩子需要至少两块饼干怎么办?
- 如果饼干有保质期限制怎么处理?
- 如果目标是让最不满意的孩子尽可能满意?
这种主动思考能显著提升算法设计能力。
