[前端]_一起刷leetcode 45. 跳跃游戏 II

142 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

题目

45. 跳跃游戏 II

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

假设你总是可以到达数组的最后一个位置。

 

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 1000

思路

做这道题目的时候一开始的困惑是,无法确定走哪条路线才是最快到达终点的,那么怎么办呢?这时候就引入了一个概念 -- 贪心算法。

那么什么是贪心算法呢? 一句话解释就是:

我们只需要把我们的问题拆分成规模更小的子问题,利用子问题的解得到原始问题的解,并且每一步我们只考虑最优解不考虑其他的情况。

具体到这道题目中,贪心的思路是:我不知道哪条路是最快的,但是我可以确保我所走的每一步都是最快的, 这样子到最后一个格子的时候自然也是最快的了。

然后来说一下具体的实现过程:

  • 首先我们定义一个长度为nums.length的数组,来记录我们到达每一个索引值所需要花费的最小步数,初始值建议为最大的数字;

  • 其次我们到达每一步的时候,遍历当前步数所能迈过的范围,这个范围内的所有值都可以根据当前位置加一步来得到,但是我们要记录的并不是这个值,而是这个值与当前位置原本步数的最小值,因为你前面走过的有可能比现在走的还快;

  • 最终通过一轮遍历,我们就可以求出到达每个位置的最小步数,返回数组的最后一个位置的最小步数即可。

实现

/**
 * @param {number[]} nums
 * @return {number}
 */
var jump = function(nums) {
    const n = nums.length;

    // 记录到达每个格子的最小步骤
    let dp = new Array(n).fill(Number.MAX_SAFE_INTEGER);

    // 一开始我们就站在第一格
    dp[0] = 0;

    // 到达最后一个格子前都有可能改变结果
    for (let i = 0; i < n - 1; i++) {
        // 每一步能走多远都试试
        for (let j = 1; j <= nums[i]; j++) {
            // 超出了边界就不处理了
            if (i + j >= n) break;
            // 每次取最小的步数,确保我们到达每个格子都是最优解
            dp[i + j] = Math.min(dp[i + j], dp[i] + 1);
        }
    }

    return dp[n - 1];
};

结果

image.png

虽然思路没问题,但是这个时间复杂度说明这道题目一定存在更优的解法,那我们继续尝试一下顺着这个思路做优化。

优化

  • 根据我们刚刚的思路,其实我们可以把数组裁剪成不同的区间,然后每一次我们记录一下下一轮可以到达的最长的距离,这样子我们只需要通过一轮遍历即可实现查找到最快的步数。

    举个例子:

    [2,3,1,1,4] 我们第一轮站在[2]的位置上,根据它可以停留的位置做个切割,得到[3, 1],这时候是我们走的第一步;

    然后根据[3, 1]所能走的位置进行切割得到[1,4],这时候走了第二步,已经到达终点了直接返回步骤即可。

优化代码

/**
 * @param {number[]} nums
 * @return {number}
 */
var jump = function(nums) {
    const len = nums.length;

    // 走了多少步
    let step = 0;

    // 当前这轮最远能走的距离
    let startIndex = 0, endIndex = 1;

    // 还没有到达终点前,一直往前走
    while (endIndex < len) {
        // 记录能走的最遥远距离
        let maxPosition = endIndex;

        for (let i = startIndex; i < endIndex; i++) {
            maxPosition = Math.max(maxPosition, i + nums[i]); 
        }

        // 遍历完了,划分下一次的区间
        startIndex = endIndex;
        endIndex = maxPosition + 1;
        // 当前步数 + 1
        step++;
    }

    return step;
};

最终结果

image.png

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。