leetcode:45. 跳跃游戏 II

题目来源

  • leetcode:jump-game-ii

题目描述

leetcode:45. 跳跃游戏 II_第1张图片

class Solution {
public:
    int jump(vector<int>& nums) {

    }
};

题目解析

从数组的第 0 个位置开始跳,跳的距离小于等于数组上对应的数。求出跳到最后个位置需要的最短步数。

模拟

  • 思想: curr表示起跳的位置,然后从这一片起跳点中选出一个能跳的最远的那个点作为新的起跳点…
    leetcode:45. 跳跃游戏 II_第2张图片
int jump(std::vector<int> arr){
    if(arr.empty()){
        return 0;
    }
    
    int step = 0;
    int cur = 0;
    int next = 0;
    for (int i = 0; i < arr.size(); ++i) {
        if(cur < i){
            step++;
            cur = next;
        }
        next = std::max(next, i + arr[i]);
    }
    return step;
}

贪心(和上面思路一样)

思路:别想那么多,就挨着跳吧

  • 如果某一个起跳点的格式可以跳跃的距离是3,那么表示后面3个格子都可以作为起跳点
  • 如果从这个起跳点叫做第1次跳跃,那么从后面3个格子起跳叫做第2次跳跃
  • 所以,当一次跳跃结束时,从下一个格子开始,到现在能跳到的最远的距离是下一次跳跃的起跳点
    • 对每一次** 跳跃** 用 for 循环来模拟。
    • 跳完一次之后,更新下一次起跳点的范围。
    • 在新的范围内跳,更新能跳到最远的距离
  • 记录跳跃次数,如果跳到了终点,就得到了结果。
    leetcode:45. 跳跃游戏 II_第3张图片
class Solution {
public:
    int jump(vector<int>& nums) {
        int ans = 0;
        int begin = 0, end = 0;   //[begin, end]
        while (end < nums.size() - 1){  //不需要检查最后一个位置是因为,最后一个位置我们不用跳了已经
            // 能跳到最远的距离
            int temp = 0;
            for (int i = begin; i <=  end; ++i) {
                temp = std::max(nums[i] + i, temp);
            }
            begin = end + 1;   // 下一次起跳点范围开始的格子
            end = temp;  // 下一次[起跳点]范围结束的格子
            ++ans;    // 跳跃次数
        }
        return ans;
    }
};

思路:顺藤摸瓜

我们每次在可跳范围内选择可以使得跳的更远的位置(为了求最小步数,我们只有当不得不跳时,才跳。)

如下图,开始的位置是 2,可跳的范围是橙色的。然后因为 3 可以跳的更远,所以跳到 3 的位置

leetcode:45. 跳跃游戏 II_第4张图片
如下图,然后现在的位置就是 3 了,能跳的范围是橙色的,然后因为 4 可以跳的更远,所以下次跳到 4 的位置。

思路:

class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size() == 1){
            return 0; 
        }
        int jump = 0;            // 记录走的最大步数
        int curDistance  = 0;  //以当前跳跃步数,能到的最远位置,比如: jump=1跳一次时,最远能到下标currJumpMax=2
        int nextDistance  = 0;  //当前位置能到的最远位置

        for (int i = 0; i < nums.size(); ++i) {
            nextDistance = max(nums[i] + i, nextDistance);  //找能跳的最远的
            if (i == curDistance){//已经走到了当前跳跃步数的边界
                if(curDistance != nums.size() - 1){  //还没有到达终点
                    jump++;//我们不得不再跳一次
                    curDistance = nextDistance; //并记录当前跳跃步数能到的最远位置
                    if (nextDistance >= nums.size() - 1) break; // 下一步的覆盖范围已经可以达到终点,结束循环
                }else{
                    break;    // 已经到达终点,不用做ans++操作了,直接结束
                }
            }
        }
        
        return jump;
    }
};

思路: 贪心策略,通过反向查找出发位置

我们的目标是到达数组的最后一个位置,因此我们可以考虑最后一步跳跃前所在的位置,该位置通过跳跃能够到达最后一个位置。

如果有多个位置通过跳跃都能够到达最后一个位置,那么我们应该如何进行选择呢?直观上看,我们可以「贪心」的选择距离最后一个位置最远的那个位置,也就是对应下标最小的那个位置。因此,我们可以从左到右遍历数组,选择第一个满足要求的位置。

找到最后一步跳跃前所在的位置后,我们继续贪心的寻找倒数第二步跳跃所在的位置,以此类推,直到找到数组的开始位置。

举个例子:
leetcode:45. 跳跃游戏 II_第5张图片

class Solution {
public:
    int jump(vector<int>& nums) {
        int pos = nums.size() - 1;  //要找的位置
        int step = 0;
        while (pos != 0){ //是否到了第 0 个位置
            for (int i = 0; i < pos; ++i) {
                if(nums[i] >= pos - i){
                    pos = i;  //更新要找的位置
                    step++;
                    break;
                }
            }
        }
        return step;
    }
};

动态规划

(1)定义状态

  • 状态:定义 dp[i] 为跳到下标 i 所需的最小跳跃次数

(2)转移方程

  • 我们知道最后一个点前面可能会有很多个点能够一步到达最后一个点。
  • 也就是:f[n−1]=min(f[n−k],…,f[n−3],f[n−2])+1。

在这里插入图片描述

(3)初始化

  • dp[0] = 0

(4)返回值

  • dp[n - 1] 即为所求
class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        std::vector<int> dp(n);
        dp[0] = 0;
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if(j + nums[j] >= i){
                    dp[i] = min(dp[i], 1 + dp[j]);
                }
            }
        }
        return dp[n - 1];
    }
};

(5)怎么优化呢?

  • 考虑集合 f[n - k],…,f[n - 3],f[n - 2]有何特性。
  • 不然发现其实必然有 f[n - k] <= …<= f[n - 3] <= f[n - 2]
  • 举个,比如我经过至少 5 步到达第 ii 个点,那么必然不可能出现使用步数少于 5 步就能达到第 i + 1i+1 个点的情况。到达第 i + 1i+1 个点的至少步数必然是 5 步或者 6 步。
  • 又因为:f[i]为到达第 i 个位置所需要的最少步数。
  • 因此当我们要求某一个 f[i]的时候,我们需要找到最早能够经过一步到达 i 点的 j 点。即有状态转移方程:f[i] = f[j] + 1
  • 也就是我们每次都贪心的取离 i 点最远的点 j 来更新 f[i]。
class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        std::vector<int> dp(n);
        //i是代表当前能跳跃的最远位置
        for (int i = 1, j = 0; i < n; ++i) {
           //如果当前能到达的位置<最远需要到达的位置
            while (j  + nums[j] < i){
                j++; //j指针后移
            }
            dp[i] = dp[j] + 1;
        }
        return dp[n - 1];
    }
};

优先队列写法:

class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n); //dp[i]: 到达i的最小跳跃次数
        priority_queue<pair<int, int>> pq;
        for (int i = 0; i < n; ++i) {
            while (!pq.empty()) {
                auto [d, j] = pq.top();
                if (j + nums[j] < i) { //从j跳不到i及i后面的位置
                    pq.pop();
                } else {
                    dp[i] = 1 - d;
                    break;
                }
            }
            pq.push({-dp[i], i});
        }
        return dp[n - 1];
    }
};

类似题目

题目
leetcode:45. 能跳到终点的最少跳跃次数 II Jump Game II
leetcode:55. 能不能跳到末尾 Jump Game

你可能感兴趣的:(算法与数据结构,leetcode,游戏,贪心算法)