LeetCode100天Day16-跳跃游戏II与H指数:贪心优化与排序查找
摘要:本文详细解析了LeetCode中两道经典题目------"跳跃游戏II"和"H指数"。通过贪心策略寻找最少跳跃次数,以及使用排序和计数计算H指数,帮助读者掌握贪心算法的优化技巧和排序统计的方法。
目录
文章目录
- LeetCode100天Day16-跳跃游戏II与H指数:贪心优化与排序查找
-
- 目录
- [1. 跳跃游戏II(Jump Game II)](#1. 跳跃游戏II(Jump Game II))
-
- [1.1 题目描述](#1.1 题目描述)
- [1.2 解题思路](#1.2 解题思路)
- [1.3 代码实现](#1.3 代码实现)
- [1.4 代码逐行解释](#1.4 代码逐行解释)
- [1.5 执行流程详解](#1.5 执行流程详解)
- [1.6 算法图解](#1.6 算法图解)
- [1.7 复杂度分析](#1.7 复杂度分析)
- [1.8 边界情况](#1.8 边界情况)
- [2. H指数(H-Index)](#2. H指数(H-Index))
-
- [2.1 题目描述](#2.1 题目描述)
- [2.2 解题思路](#2.2 解题思路)
- [2.3 代码实现](#2.3 代码实现)
- [2.4 代码逐行解释](#2.4 代码逐行解释)
- [2.5 执行流程详解](#2.5 执行流程详解)
- [2.6 算法图解](#2.6 算法图解)
- [2.7 复杂度分析](#2.7 复杂度分析)
- [2.8 边界情况](#2.8 边界情况)
- [3. 两题对比与总结](#3. 两题对比与总结)
-
- [3.1 算法对比](#3.1 算法对比)
- [3.2 贪心算法优化](#3.2 贪心算法优化)
- [3.3 排序查找模板](#3.3 排序查找模板)
- [3.4 计数排序的应用](#3.4 计数排序的应用)
- [4. 总结](#4. 总结)
- 参考资源
- 文章标签
1. 跳跃游戏II(Jump Game II)
1.1 题目描述
给定一个长度为 n 的 0索引 整数数组 nums。初始位置在下标 0。
每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在索引 i 处,你可以跳转到任意 (i + j) 处:
0 <= j <= nums[i]i + j < n
返回到达 n - 1 的最小跳跃次数。测试用例保证可以到达 n - 1。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是2。
从下标为0跳到下标为1的位置,跳1步,然后跳3步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
1.2 解题思路
这道题使用贪心算法:
- 每次选择能跳到最远位置的位置
- 维护当前能到达的最远位置
- 当遍历到当前边界时,增加跳跃次数,更新边界
- 返回跳跃次数
解题步骤:
- 边界检查:数组长度≤1,返回0
- 初始化len为nums[0],k为跳跃次数,ini为起始位置
- 当len < n-1时,继续跳跃
- 在ini到len范围内,找到能跳到最远的位置
- 更新ini和len,增加k
- 返回k+1
1.3 代码实现
java
class Solution {
public int jump(int[] nums) {
if(nums.length <= 1){
return 0;
}
int len = nums[0];
int k = 0;
int ini = 0;
while(len < nums.length-1){
int temp = 0;
int index = 0;
for(int i = ini;i <= len;i++){
if(update(nums,i) > temp){
index = i;
temp = update(nums,i);
}
}
ini = len;
len = temp;
k++;
}
return ++k;
}
public int update(int[] nums,int index){
return index + nums[index];
}
}
1.4 代码逐行解释
第一部分:边界检查
java
if(nums.length <= 1){
return 0;
}
功能:处理特殊情况
| nums | 长度 | 说明 | 返回值 |
|---|---|---|---|
[1] |
1 | 已经在终点 | 0 |
[] |
0 | 空数组 | 0 |
[1,2] |
2 | 需要跳跃 | - |
第二部分:初始化
java
int len = nums[0];
int k = 0;
int ini = 0;
变量说明:
| 变量 | 初始值 | 含义 |
|---|---|---|
len |
nums[0] | 当前能跳到的最远位置 |
k |
0 | 跳跃次数(不包含最后一次) |
ini |
0 | 上一次跳跃的起点 |
变量关系:
nums = [2, 3, 1, 1, 4]
索引: 0 1 2 3 4
ini = 0: 上一次从位置0开始
len = nums[0] = 2: 从位置0能跳到位置2
第三部分:寻找最优跳跃
java
while(len < nums.length-1){
int temp = 0;
int index = 0;
for(int i = ini;i <= len;i++){
if(update(nums,i) > temp){
index = i;
temp = update(nums,i);
}
}
ini = len;
len = temp;
k++;
}
循环条件:
java
while(len < nums.length-1)
| 条件 | 说明 |
|---|---|
len < nums.length-1 |
当前最远位置还未到达终点 |
内层循环:
java
for(int i = ini;i <= len;i++){
if(update(nums,i) > temp){
index = i;
temp = update(nums,i);
}
}
| 变量 | 作用 |
|---|---|
i |
遍历当前可到达的范围 |
update(nums,i) |
计算从位置i能跳到的最远位置 |
temp |
记录最远位置 |
index |
记录最优起跳位置 |
update函数:
java
public int update(int[] nums,int index){
return index + nums[index];
}
| 参数 | 返回值 | 说明 |
|---|---|---|
index |
index + nums[index] |
从index能跳到的最远位置 |
第四部分:更新状态
java
ini = len;
len = temp;
k++;
| 操作 | 说明 |
|---|---|
ini = len |
更新起点为上一次的边界 |
len = temp |
更新边界为新的最远位置 |
k++ |
跳跃次数加1 |
第五部分:返回结果
java
return ++k;
为什么要++k:
while循环中的k记录的是"中间"跳跃次数
最后一次跳跃没有进入循环
所以需要++k加上最后一次
例如:
nums = [2, 3, 1, 1, 4]
len初始=2
while(2 < 4): 进入
找到最优位置i=1,能跳到4
ini=2, len=4, k=1
while(4 < 4): 不进入
返回 ++k = 2
1.5 执行流程详解
示例1 :nums = [2,3,1,1,4]
初始状态:
nums = [2, 3, 1, 1, 4]
索引: 0 1 2 3 4
len = 2
k = 0
ini = 0
第1次循环:len=2 < 4
遍历 i=0 到 2:
i=0: update(nums,0) = 0 + 2 = 2
2 > temp(0)? 是
temp = 2, index = 0
i=1: update(nums,1) = 1 + 3 = 4
4 > temp(2)? 是
temp = 4, index = 1
i=2: update(nums,2) = 2 + 1 = 3
3 > temp(4)? 否
ini = 2, len = 4, k = 1
第2次循环:len=4 < 4? 否,退出
返回 ++k = 2
输出: 2
示例2 :nums = [2,3,0,1,4]
初始状态:
nums = [2, 3, 0, 1, 4]
索引: 0 1 2 3 4
len = 2
k = 0
ini = 0
第1次循环:len=2 < 4
遍历 i=0 到 2:
i=0: update(nums,0) = 0 + 2 = 2
temp = 2, index = 0
i=1: update(nums,1) = 1 + 3 = 4
4 > 2? 是
temp = 4, index = 1
i=2: update(nums,2) = 2 + 0 = 2
2 > 4? 否
ini = 2, len = 4, k = 1
第2次循环:len=4 < 4? 否,退出
返回 ++k = 2
输出: 2
1.6 算法图解
nums = [2, 3, 1, 1, 4]
索引: 0 1 2 3 4
第1次跳跃:
当前位置: 0
nums[0] = 2,能跳到位置1或2
选择范围: 位置1和2
位置1: update = 1 + 3 = 4
位置2: update = 2 + 1 = 3
选择位置1(能跳到4)
跳跃: 0 → 1
次数: k = 1
第2次跳跃:
当前位置: 1
nums[1] = 3,能跳到位置2, 3, 4
已经可以到达4
总跳跃次数: 2
路径: 0 → 1 → 4
nums = [2, 3, 0, 1, 4]
索引: 0 1 2 3 4
第1次跳跃:
从位置0,能跳到1或2
位置1: 1 + 3 = 4 (最优)
位置2: 2 + 0 = 2
选择位置1
跳跃: 0 → 1 → 4
次数: 2
1.7 复杂度分析
| 分析维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n²) | 最坏情况每步遍历整个范围 |
| 空间复杂度 | O(1) | 只使用常数空间 |
优化思路:BFS贪心优化到O(n)
java
// 优化版本:一次遍历
class Solution {
public int jump(int[] nums) {
if (nums.length <= 1) return 0;
int jumps = 0;
int currentEnd = 0;
int farthest = 0;
for (int i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i == currentEnd) {
jumps++;
currentEnd = farthest;
}
}
return jumps;
}
}
1.8 边界情况
| nums | 说明 | 输出 |
|---|---|---|
[1] |
单个元素 | 0 |
[1,2] |
两元素 | 1 |
[2,1] |
可直接跳到终点 | 1 |
[1,1,1,1] |
每次只能跳一步 | 3 |
2. H指数(H-Index)
2.1 题目描述
给你一个整数数组 citations,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h指数。
根据维基百科上 h指数 的定义:h 代表"高引用次数",一名科研人员的 h指数 是指他(她)的(n篇论文中)总共 有 h篇论文 分别被引用了至少h次 。其余的 n - h篇论文 每篇被引用 不多于 h次。
如果 h 有多种可能的值,h指数 是其中最大的那个。
示例 1:
输入:citations = [3,0,6,1,5]
输出:3
解释:给定数组表示研究者总共有5篇论文,每篇论文相应的被引用了3, 0, 6, 1, 5次。
由于研究者有3篇论文每篇至少被引用了3次,其余两篇论文每篇被引用不多于3次,所以她的h指数是3。
示例 2:
输入:citations = [1,3,1]
输出:1
2.2 解题思路
这道题使用排序和计数的方法:
- 对数组进行降序排序
- 从后向前遍历,找到最大的h满足至少h篇论文被引用至少h次
- 返回h
解题步骤:
- 对数组排序
- 从数组末尾开始向前遍历
- 统计有多少篇论文的引用次数大于h
- 当引用次数不大于h时停止
- 返回h
2.3 代码实现
java
class Solution {
public int hIndex(int[] citations) {
Arrays.sort(citations);
int h = 0, i = citations.length - 1;
while (i >= 0 && citations[i] > h) {
i--;
h++;
}
return h;
}
}
2.4 代码逐行解释
第一部分:排序
java
Arrays.sort(citations);
功能:对数组进行升序排序
| 排序前 | 排序后 |
|---|---|
[3, 0, 6, 1, 5] |
[0, 1, 3, 5, 6] |
[1, 3, 1] |
[1, 1, 3] |
第二部分:从后向前查找
java
int h = 0, i = citations.length - 1;
while (i >= 0 && citations[i] > h) {
i--;
h++;
}
变量说明:
| 变量 | 初始值 | 含义 |
|---|---|---|
h |
0 | H指数 |
i |
length-1 | 当前检查的论文索引(从后向前) |
循环条件:
java
while (i >= 0 && citations[i] > h)
| 条件 | 说明 |
|---|---|
i >= 0 |
还有论文可以检查 |
citations[i] > h |
当前论文的引用次数大于h |
循环体:
java
i--;
h++;
| 操作 | 说明 |
|---|---|
i-- |
移动到前一篇论文 |
h++ |
H指数加1 |
2.5 执行流程详解
示例1 :citations = [3,0,6,1,5]
初始状态:
citations = [3, 0, 6, 1, 5]
排序后: citations = [0, 1, 3, 5, 6]
索引: 0 1 2 3 4
h = 0
i = 4
第1次循环:
i=4 >= 0? 是
citations[4]=6 > h=0? 是
i = 3, h = 1
第2次循环:
i=3 >= 0? 是
citations[3]=5 > h=1? 是
i = 2, h = 2
第3次循环:
i=2 >= 0? 是
citations[2]=3 > h=2? 是
i = 1, h = 3
第4次循环:
i=1 >= 0? 是
citations[1]=1 > h=3? 否,退出循环
返回 h = 3
输出: 3
示例2 :citations = [1,3,1]
初始状态:
citations = [1, 3, 1]
排序后: citations = [1, 1, 3]
索引: 0 1 2
h = 0
i = 2
第1次循环:
i=2 >= 0? 是
citations[2]=3 > h=0? 是
i = 1, h = 1
第2次循环:
i=1 >= 0? 是
citations[1]=1 > h=1? 否,退出循环
返回 h = 1
输出: 1
2.6 算法图解
citations = [3, 0, 6, 1, 5]
排序后: [0, 1, 3, 5, 6]
H指数定义:
有h篇论文,每篇至少被引用h次
从后向前(从大到小)查找:
h=0: 检查citations[4]=6
6 > 0? 是
有1篇论文至少引用0次
h=1: 检查citations[3]=5
5 > 1? 是
有2篇论文至少引用1次
h=2: 检查citations[2]=3
3 > 2? 是
有3篇论文至少引用2次
h=3: 检查citations[1]=1
1 > 3? 否
最多有3篇论文至少引用3次
H指数 = 3
验证:
论文引用次数: [0, 1, 3, 5, 6]
至少3次: [3, 5, 6] → 3篇 ✓
2.7 复杂度分析
| 分析维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 排序的复杂度 |
| 空间复杂度 | O(1) | 取决于排序实现 |
优化思路:计数排序优化到O(n)
java
// 优化版本:计数排序
class Solution {
public int hIndex(int[] citations) {
int n = citations.length;
int[] count = new int[n + 1];
for (int c : citations) {
if (c >= n) {
count[n]++;
} else {
count[c]++;
}
}
int total = 0;
for (int i = n; i >= 0; i--) {
total += count[i];
if (total >= i) {
return i;
}
}
return 0;
}
}
2.8 边界情况
| citations | 说明 | 输出 |
|---|---|---|
[0] |
无引用 | 0 |
[1] |
单篇引用 | 1 |
[100] |
高引用 | 1 |
[0,0] |
无引用 | 0 |
3. 两题对比与总结
3.1 算法对比
| 对比项 | 跳跃游戏II | H指数 |
|---|---|---|
| 核心算法 | 贪心算法 | 排序+计数 |
| 数据结构 | 数组 | 数组 |
| 时间复杂度 | O(n²) | O(n log n) |
| 空间复杂度 | O(1) | O(1) |
| 应用场景 | 最少步数问题 | 统计问题 |
3.2 贪心算法优化
方法一:遍历范围(O(n²))
java
for (int i = ini; i <= len; i++) {
if (update(nums, i) > temp) {
temp = update(nums, i);
}
}
方法二:一次遍历(O(n))
java
for (int i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i == currentEnd) {
jumps++;
currentEnd = farthest;
}
}
3.3 排序查找模板
java
// 排序后从后向前查找
Arrays.sort(array);
int i = array.length - 1;
int result = 0;
while (i >= 0 && array[i] > result) {
i--;
result++;
}
return result;
3.4 计数排序的应用
适用场景:
- 元素值范围有限
- 需要统计频率
java
// 计数排序模板
int[] count = new int[maxValue + 1];
for (int num : array) {
count[num]++;
}
// 从后向前统计
int total = 0;
for (int i = maxValue; i >= 0; i--) {
total += count[i];
if (满足条件) {
return i;
}
}
4. 总结
今天我们学习了两道优化算法题目:
- 跳跃游戏II:掌握贪心算法寻找最少跳跃次数,理解局部最优导致全局最优
- H指数:掌握排序后从后向前查找,理解H指数的定义和计算
核心收获:
- 贪心算法每次选择能跳到最远的位置
- 从后向前遍历可以简化统计逻辑
- 排序后相同值会连续,便于计数
- 计数排序可以在特定条件下优化时间复杂度
- H指数的核心是有h篇论文至少被引用h次
练习建议:
- 用一次遍历的方法优化跳跃游戏II
- 学习计数排序优化H指数
- 思考如何处理H指数的变种问题
参考资源
文章标签
#LeetCode #算法 #Java #贪心算法 #排序
喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!