LeetCode100天Day16-跳跃游戏II与H指数

LeetCode100天Day16-跳跃游戏II与H指数:贪心优化与排序查找

摘要:本文详细解析了LeetCode中两道经典题目------"跳跃游戏II"和"H指数"。通过贪心策略寻找最少跳跃次数,以及使用排序和计数计算H指数,帮助读者掌握贪心算法的优化技巧和排序统计的方法。

目录

文章目录

1. 跳跃游戏II(Jump Game II)

1.1 题目描述

给定一个长度为 n0索引 整数数组 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. 每次选择能跳到最远位置的位置
  2. 维护当前能到达的最远位置
  3. 当遍历到当前边界时,增加跳跃次数,更新边界
  4. 返回跳跃次数

解题步骤

  1. 边界检查:数组长度≤1,返回0
  2. 初始化len为nums[0],k为跳跃次数,ini为起始位置
  3. 当len < n-1时,继续跳跃
  4. 在ini到len范围内,找到能跳到最远的位置
  5. 更新ini和len,增加k
  6. 返回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 执行流程详解

示例1nums = [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

示例2nums = [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 解题思路

这道题使用排序和计数的方法:

  1. 对数组进行降序排序
  2. 从后向前遍历,找到最大的h满足至少h篇论文被引用至少h次
  3. 返回h

解题步骤

  1. 对数组排序
  2. 从数组末尾开始向前遍历
  3. 统计有多少篇论文的引用次数大于h
  4. 当引用次数不大于h时停止
  5. 返回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 执行流程详解

示例1citations = [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

示例2citations = [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. 总结

今天我们学习了两道优化算法题目:

  1. 跳跃游戏II:掌握贪心算法寻找最少跳跃次数,理解局部最优导致全局最优
  2. H指数:掌握排序后从后向前查找,理解H指数的定义和计算

核心收获

  • 贪心算法每次选择能跳到最远的位置
  • 从后向前遍历可以简化统计逻辑
  • 排序后相同值会连续,便于计数
  • 计数排序可以在特定条件下优化时间复杂度
  • H指数的核心是有h篇论文至少被引用h次

练习建议

  1. 用一次遍历的方法优化跳跃游戏II
  2. 学习计数排序优化H指数
  3. 思考如何处理H指数的变种问题

参考资源

文章标签

#LeetCode #算法 #Java #贪心算法 #排序

喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!

相关推荐
mit6.8245 小时前
两个有序集合|状态分析
算法
平生不喜凡桃李5 小时前
LeetCode 两数之和/三数之和
算法·leetcode·两数之和·三数之和
C雨后彩虹5 小时前
中文分词模拟器
java·数据结构·算法·华为·面试
BLi4ee5 小时前
【Scholarly Notes】Adaptive Model Pruning for Federated Learning
算法·机器学习·剪枝
Remember_9935 小时前
【LeetCode精选算法】二分查找专题二
java·数据结构·算法·leetcode·哈希算法
We་ct5 小时前
LeetCode 42. 接雨水:双指针解法深度剖析与全方法汇总
前端·算法·leetcode·typescript
液态不合群5 小时前
如何提升 C# 应用中的性能
开发语言·算法·c#
诗远Yolanda5 小时前
EI国际会议-通信技术、电子学与信号处理(CTESP 2026)
图像处理·人工智能·算法·计算机视觉·机器人·信息与通信·信号处理
程序员-King.6 小时前
day165—递归—最长回文子序列(LeetCode-516)
算法·leetcode·深度优先·递归