1. 跳跃游戏II:贪心策略的实战解析
今天要啃的硬骨头是LeetCode第45题"跳跃游戏II",这道题在周赛中出现的频率相当高。题目要求给定一个非负整数数组nums,你最初位于数组的第一个位置,数组中的每个元素代表你在该位置可以跳跃的最大长度,目标是使用最少的跳跃次数到达数组的最后一个位置。
1.1 问题本质与暴力解法
先看个具体例子:nums = [2,3,1,1,4]。从索引0出发,可以跳1或2步:
- 跳1步到索引1(值为3),再跳3步直接到终点,共2跳
- 跳2步到索引2(值为1),再跳1步到索引3(值为1),最后跳1步到终点,共3跳
显然最少需要2跳。暴力解法可以用BFS,把每个位置能跳到的位置作为下一层节点,直到到达终点。但这样时间复杂度是O(n²),对于10^4量级的输入会超时。
1.2 贪心算法的优化思路
更聪明的做法是贪心算法,核心思想是:在每一步跳跃时,选择能使得"下一步能跳得最远"的位置。具体实现需要维护三个变量:
- current_end:当前跳跃能到达的边界
- farthest:当前能跳到的最远位置
- jumps:跳跃次数
Python实现代码:
python复制def jump(nums):
jumps = 0
current_end = farthest = 0
for i in range(len(nums)-1):
farthest = max(farthest, i + nums[i])
if i == current_end:
jumps += 1
current_end = farthest
return jumps
这个解法的时间复杂度是O(n),空间复杂度O(1)。关键点在于理解为什么这样能保证最优解:每次跳跃都尽可能扩大下一步的选择范围,相当于在局部最优中达到全局最优。
1.3 边界条件与调试技巧
实际编码时容易踩的坑:
- 循环范围应该是len(nums)-1而非len(nums),因为到达最后一个位置时不需要再跳
- 初始条件设置要正确,特别是current_end和farthest都从0开始
- 对于nums = [0]的特殊情况,应该返回0
调试时可以打印每次跳跃后的current_end和farthest值,观察是否符合预期。例如对于nums = [2,3,1,1,4]:
- i=0: farthest=2, current_end=0 → jump=1, current_end=2
- i=1: farthest=4
- i=2: farthest=4, current_end=2 → jump=2, current_end=4
- 循环结束
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. H指数:排序与计数的双重解法
接下来是LeetCode第274题"H指数",这道题在学术评价系统中很常见。定义:一名科研人员的h指数是指他/她有h篇论文被引用了至少h次,且其他N-h篇论文被引用次数不超过h次。
2.1 排序法的直观解法
最直观的方法是先排序再查找。将引用次数降序排列,找到第一个位置i使得citations[i] < i+1,此时的i就是h指数。
Python实现:
python复制def hIndex(citations):
citations.sort(reverse=True)
for i in range(len(citations)):
if citations[i] < i + 1:
return i
return len(citations)
例如citations = [3,0,6,1,5]:
- 排序后[6,5,3,1,0]
- i=0: 6>=1
- i=1: 5>=2
- i=2: 3>=3
- i=3: 1<4 → h=3
时间复杂度O(nlogn)来自排序,空间复杂度O(1)(原地排序)或O(n)(非原地)。
2.2 计数法的优化方案
当论文数量n很大但单篇引用次数较小时(比如不超过n),可以用计数排序的思想达到O(n)时间复杂度:
- 统计引用次数≥i的论文数量count[i]
- 从高到低找最大的i使得count[i] >= i
Python实现:
python复制def hIndex(citations):
n = len(citations)
count = [0] * (n + 1)
for c in citations:
count[min(c, n)] += 1
total = 0
for i in range(n, -1, -1):
total += count[i]
if total >= i:
return i
return 0
对于citations = [3,0,6,1,5]:
- count = [1,1,0,1,0,2](索引0~5)
- i=5: total=2 <5
- i=4: total=2 <4
- i=3: total=3 >=3 → h=3
3. 贪心算法的深入理解与应用场景
贪心算法在跳跃游戏问题中展现了强大的威力,但它并非万能钥匙。理解其适用条件至关重要:
3.1 贪心算法的适用条件
- 最优子结构:问题的最优解包含子问题的最优解
- 贪心选择性质:局部最优选择能导致全局最优解
在跳跃游戏II中,每次选择能跳得最远的位置,正好满足这两个条件。对比动态规划,贪心算法通常更高效但适用面更窄。
3.2 贪心算法的常见应用
- 区间调度问题(选择最多不重叠区间)
- 霍夫曼编码(数据压缩)
- Dijkstra算法(最短路径)
- Kruskal/Prim算法(最小生成树)
3.3 贪心算法的证明技巧
要证明贪心算法的正确性,常用方法:
- 交换论证:假设存在更优解,通过交换元素得到矛盾
- 归纳法:证明每个步骤的选择保持最优性
- 拟阵理论:某些问题可以建模为拟阵
对于跳跃游戏II,可以用归纳法证明:假设前k步是最优的,那么第k+1步选择能跳得最远的位置也必然最优。
4. 排序算法的选择与性能考量
H指数问题的两种解法展示了排序在不同场景下的应用:
4.1 排序法 vs 计数法
| 维度 | 排序法 | 计数法 |
|---|---|---|
| 时间复杂度 | O(nlogn) | O(n) |
| 空间复杂度 | O(1)或O(n) | O(n) |
| 适用场景 | 通用 | 元素范围有限时 |
| 实现难度 | 简单 | 中等 |
4.2 Python排序的内部机制
Python的list.sort()使用Timsort算法,是归并排序和插入排序的混合体:
- 最坏情况O(nlogn)
- 最好情况O(n)(已排序)
- 稳定排序
- 空间复杂度O(n)
对于H指数问题,如果引用次数范围很大(如某些论文被引数万次),排序法可能更合适。
4.3 工程实践中的排序优化
在实际项目中:
- 对小数组(n<100)可能直接用插入排序
- 对基本有序数组,Timsort表现极佳
- 对特定范围整数,计数排序/桶排序可能更好
- 内存紧张时需要考虑原地排序
在H指数问题中,如果n很大但引用次数范围小(如0~1000),可以先用计数法统计频次,再模拟排序过程,进一步优化空间。
