当前位置: 首页 > news >正文

力扣最热一百题——跳跃游戏II


目录

题目链接:45. 跳跃游戏 II - 力扣(LeetCode)

题目描述

解法一:动态规划

关键点

Java写法:

C++写法:

运行时间

时间复杂度和空间复杂度

解法二:贪心算法

Java写法:

C++写法:

运行时间

时间复杂度和空间复杂度

时间复杂度

空间复杂度

总结

总结



题目链接:45. 跳跃游戏 II - 力扣(LeetCode)

注:下述题目描述和示例均来自力扣


题目描述

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i] 
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

示例 1:

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:

输入: nums = [2,3,0,1,4]
输出: 2

提示:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 1000
  • 题目保证可以到达 nums[n-1]

解法一:动态规划

  1. 初始化:

    • 首先确定输入数组的长度 len
    • 创建一个一维数组 dp 来存储到达每个位置所需的最小跳跃次数,初始时将 dp[0] 设置为 0(因为起点不需要跳跃),其余值设置为 0 或者默认值。
  2. 动态规划填充 dp 数组:

    • 从索引 1 开始遍历到数组的末尾(即外层循环)。
    • 对于每一个位置 i,尝试从前一个位置(j,范围是从 0 到 i-1)跳过来,并检查是否可以从位置 j 跳跃到位置 i(条件:j + nums[j] >= i)。
    • 如果可以从 j 跳到 i,并且这种跳法所需跳跃次数比目前记录的要少(即 dp[j] + 1 < minStep),则更新 minStep 为 dp[j] + 1
    • 外层循环每次结束时,将 minStep 的值赋给 dp[i],表示到达位置 i 所需的最小跳跃次数。
  3. 返回结果:

    • 最后返回 dp 数组的最后一个元素,即到达数组最后一个位置所需的最小跳跃次数。

关键点

  • 初始化和边界情况处理:注意到对于起始位置 dp[0] = 0,因为从起点开始不需要任何跳跃。此外,minStep 初始化为一个较大的值(如 10001),以确保任意可行路径都会被选择作为更优解。

Java写法:

class Solution {
    public int jump(int[] nums) {
        int len = nums.length;

        // 定义出DP数组
        // 对应位置的值表示,跳跃到当前位置所需的最小跳跃次数
        int[] dp = new int[len];
        // 初始化第一位
        dp[0] = 0;

        for(int i = 1;i < len; i++){
            // 这里找到i这个位置所需的最少次数(为什么初始化为10001看题目提示)
            int minStep = 10001;
            for(int j = 0; j < i;j++){
                // 如果从j的位置可以跳过来,而且比minStep次数少,更新
                if(j + nums[j] >= i && dp[j] + 1 < minStep){
                    minStep = dp[j] + 1;
                }
            }
            // 到这里之后dp[i]存储的就是最小的跳跃次数了
            dp[i] = minStep;
        }
        return dp[len - 1];
    }
}

C++写法:

#include <vector>
#include <algorithm> // 用于使用min函数

class Solution {
public:
    int jump(std::vector<int>& nums) {
        int len = nums.size();
        
        // 定义出DP数组
        // 对应位置的值表示,跳跃到当前位置所需的最小跳跃次数
        std::vector<int> dp(len, 0); // 初始化dp数组,默认所有元素都是0
        
        for(int i = 1; i < len; ++i){
            // 这里找到i这个位置所需的最少次数(为什么初始化为10001看题目提示)
            int minStep = 10001;
            for(int j = 0; j < i; ++j){
                // 如果从j的位置可以跳过来,而且比minStep次数少,更新
                if(j + nums[j] >= i && dp[j] + 1 < minStep){
                    minStep = dp[j] + 1;
                }
            }
            // 到这里之后dp[i]存储的就是最小的跳跃次数了
            dp[i] = minStep;
        }
        return dp[len - 1]; // 返回dp数组的最后一个元素,即到达最后一步的最小跳跃次数
    }
};

运行时间

时间复杂度和空间复杂度

        外层循环遍历数组中的每个元素(除了第一个元素),内层循环则是为了找到到达当前索引位置所需的最少跳跃次数:

  • 外层循环从 i = 1 到 len - 1,总共执行 len - 1 次。
  • 对于每次外层循环,内层循环从 j = 0 遍历到 i - 1。在最坏的情况下,即数组中的每个元素都允许跳到数组的任意位置时,内层循环需要对每个 i 都进行 i 次比较和可能的更新操作。

那么总的时间复杂度是所有内层循环执行次数的总和,可以表示为:

1+2+...+(n-1)=O(n^2)

其中 n 是输入数组 nums 的长度。这意味着,在最坏情况下,算法的时间复杂度是 O(n^2)

        空间复杂度方面,主要是由于使用了一个大小为 ndp 数组来存储到达每个位置所需的最小跳跃次数。因此,空间复杂度是 O(n)。

总结:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)




解法二:贪心算法

  1. 初始化:

    • 定义三个变量:
      • currentEnd:当前跳跃能够达到的最远位置。
      • farthest:遍历过程中能够达到的最远位置。
      • jumps:记录跳跃次数。
  2. 遍历数组:

    • 从数组的起始位置开始遍历直到倒数第二个元素(因为我们只需要知道到达最后一个位置前的最远可达范围)。
    • 对于每个位置 i,更新 farthest 为 max(farthest, i + nums[i]),即计算从当前位置出发可以达到的最远位置。
    • 如果当前索引 i 等于 currentEnd,意味着我们已经到达了本次跳跃的最远距离,则需要进行一次新的跳跃,并将 currentEnd 更新为 farthest,同时增加跳跃计数 jumps
  3. 终止条件:

    • 当 currentEnd 大于或等于数组的最后一个索引时,表示可以通过之前确定的跳跃策略到达或超过最后一个位置,此时返回 jumps 即可。

Java写法:

class Solution {
    public int jump(int[] nums) {
        // currentEnd:当前跳跃能够达到的最远位置。
        // farthest:遍历过程中能够达到的最远位置。
        // jumps:记录跳跃次数。
        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; 
                
                // 如果在下一次跳跃能到达或超过终点,则提前结束循环
                if (currentEnd >= nums.length - 1) {
                    break;
                }
            }
        }
        return jumps;
    }
}

C++写法:

class Solution {
public:
    int jump(vector<int>& nums) {
        int jumps = 0, currentEnd = 0, farthest = 0;
        for (int i = 0; i < nums.size() - 1; ++i) {
            farthest = std::max(farthest, i + nums[i]);
            if (i == currentEnd) {
                jumps++;
                currentEnd = farthest;
                if (currentEnd >= nums.size() - 1)
                    break; // 提前退出循环
            }
        }
        return jumps;
    }
};

运行时间

时间复杂度和空间复杂度

时间复杂度

  • O(n):仅遍历一次数组。对于数组中的每个元素,我们会检查并更新能够跳跃到的最远距离 farthest,并在达到当前跳跃边界 currentEnd 时进行跳跃计数和边界的更新。由于每个元素只被访问一次,因此时间复杂度是线性的,即 O(n)O(n),其中 nn 是数组 nums 的长度。

空间复杂度

  • O(1):使用了常数级的额外空间,它只需要跳跃次数 (jumps)、当前跳跃能到达的最远位置 (currentEnd) 和遍历过程中计算出的能到达的最远位置 (farthest) 等来存储。无论输入数组的大小如何,所使用的额外空间大小保持不变,因此空间复杂度为 O(1)。

总结

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)


总结

        EZEZ,但是贪心的思路真的是不如DP那样的清楚明确,所以还是各有所爱吧算是。

相关文章:

  • 诺视Micro-LED微显示芯片量产线投产 ——开启微显示技术新时代
  • 【cf】交换
  • 智能跳低成本otp语音芯片方案-wt6020 语音计数时间提示:“100次”“运动时间5分钟
  • 什么是大数据风控
  • 以光盘读写系统演示面向对象设计的原则与方法
  • 基于python的Flask模块化设计与蓝图的妙用——打造轻量化Web应用
  • VSCode扩展工具Copilot MCP使用教程【MCP】
  • Springboot实战篇(1):项目概述及环境搭建
  • Vmware中的centos7连接上网
  • 递归、搜索与回溯第三讲:综合练习
  • 在 web 部署 YOLOv8目标检测(Django+html)
  • shopee商品列表数据接口详解
  • Gymnasium Cart Pole 环境与 REINFORCE 算法 —— 强化学习入门 2
  • 【视频】OrinNX+Ubuntu20.04:移植OpenCV-4.11.0 with CUDA(含opencv_contrib )
  • 教材结构化解读
  • scrollIntoView 的behavior都有哪些属性
  • 高性能边缘计算网关-高算力web组态PLC网关
  • 《通用去条纹算法:兼容自然图像与荧光图像的频域滤波方法》
  • 走进Java:String字符串的基本使用
  • 2000-2016年各省地方财政营业税数据
  • 晋城疫情防控最新消息今天封城了/seo专员是什么职位
  • b2c商城网站开发价格/百度知道官网首页登录入口
  • 网站建设开发程序/win7优化设置
  • 网推是什么意思/谷歌seo优化推广
  • 手机网站移动应用/seo赚钱吗
  • 做网站好的/软文写作的三个要素