55. Jump Game(跳跃游戏)三种解法(C++ & 注释)

1. 题目描述

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

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

判断你是否能够到达最后一个位置。

示例 1:

输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3步到达最后一个位置。

示例 2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 ,所以你永远不可能到达最后一个位置。

题目链接:中文题目英文题目

2. 回溯法(Backtracking, Time Limit Exceeded)

2.1 解题思路

对于这道题,回朔法是非常直接的思路:

  1. 从序号(index) = 0开始,取nums[index]的最大距离,跳到下一个index + nums[index]序号位置;
  2. 如果下一个nums[index]大于0,则继续1. 的步骤,直到抵到右边界的序号,返回true;
  3. 反之,如果下一个nums[index]为0,则返回上一个序号位置,取第二大的距离,跳到下一个序号,继续1. 的步骤,直到回到初始序号,此时表示不可能抵达右边界的序号,返回false;

下面的流程图直观地表示了上面的步骤:
在这里插入图片描述

当然1. 可以先取最小值,再取最大值,但是这样会稍微慢一些,我们可以优先取最大值,保证每次的移动的距离最大。

可惜的是,这个方法对于某些特殊的test case是会超时的,一般都是右边有0,比如下面两:1) test case1;2)test case2

2.2 实例代码

class Solution {
    bool backTracking(vector<int>& nums, int i) {
        if (i + nums[i] >= static_cast<int>(nums.size()) - 1) return true; // static_cast<int> - 因为需要用unsigned - 1,转换成int比较保险,否则0 - 1会溢出
        // if (!nums[i]) return false; // 添加这句可以通过test case1,但是另一个不行
        for (int j = nums[i]; j > 0; j--) if (backTracking(nums, j + i)) return true;
        return false;
    }

public:
    bool canJump(vector<int>& nums) { return backTracking(nums, 0); }
};

3. 动态规划(Dynamic Programming)

3.1 解题思路

动态规划有两种思路:1)使用动态规划的回朔法;2)动态规划;

首先,我们先来看看2. 回溯法里面的第二个例子,我们发现1(序号2)无论如何都会走到0(序号3),2(序号1)也是最终都会走到0,无论是直接走两步来到0,还是先走到1,再走到0。所以我们知道这两个序号是不可能抵到终点,所以3(序号1)取到这两个序号的值一定是无效的,所以如果在之前回溯的过程中,我们记录下那些不可能抵达终点的序号和可以抵达终点的序号,分别标记为:GOON,NOWAY,如果来到这些有标记的点,GOON直接返回true,NOWAY直接返回false,这样就省去了一些递归过程。

对于没有标记的点,我们可以统一标记为UNKNOWN,正常进行2. 的回溯即可。具体的流程图如下:
在这里插入图片描述
可惜C++版本的这个思路还是超时~

但是走到这里,我们观察到一个重要的线索:如果我们能从后面开始动态规划,从后面记录下每个点是否能抵达终点。所以根据这个思路,我们可以写出动态规划的方程:

dp[i] = dp[i + k], 1 < k <= nums[i]

我们以下面这个例子来进行说明:

[0, 1, 2, 3, 4, 5, 6]
[2, 4, 2, 1, 0, 2, 0]

dp[5]能否达到6,取决于dp[5 + 1], dp[5 + 2](越界忽略),dp[6]默认能达到终点,所以dp[5] = GOON;以此类推,最后我们只需检查dp[0]是否等于GOON即可。具体的流程见下图:
在这里插入图片描述

3.2 实例代码

typedef enum explores { GOON, NOWAY, UNKNOWN } explores;

class Solution {
    // 下面两个方法二选一即可
    // 1.使用动态规划的回朔法, Time Limit Exceeded
    bool backTrackingUsingDPTopDown(vector<int>& nums, int i, vector<explores>& indices) {
        if (indices[i] != UNKNOWN) {
        	return indices[i] == GOON ? true : false;
        }
        
        if (i + nums[i] >= static_cast<int>(nums.size()) - 1) { 
        	indices[i] = GOON; 
        	return true;
         }
         
        for (int j = nums[i]; j > 0; j--) {
        	if (backTrackingUsingDPTopDown(nums, j + i, indices)) { 
        		indices[i] = GOON; return true; 
        	}
        }
        
        indices[i] = NOWAY; return false;
    }

    // 2. 动态规划
    bool UsingDPBottomUp(vector<int>& nums, vector<explores>& indices) {
        indices[nums.size() - 1] = GOON;
        for (int i = nums.size() - 2; i >= 0; i--) {
            int furestJump = min(nums[i] + i, static_cast<int>(nums.size()) - 1);
            for (int j = i + 1; j <= furestJump; j++) { 
            	if (indices[j] == GOON) { 
            		indices[i] = GOON; 
            		break; 
            	}
            }
        }
        return indices[0] == GOON;
    }

public:
    bool canJump(vector<int>& nums) {
        vector<explores> indices(nums.size(), UNKNOWN);
        //return UsingDPBottomUp(nums, indices);
        return UsingDPBottomUp(nums, indices);
    }
};

4. 贪心算法(Greedy)

4.1 解题思路

最后,我们来看看第二个动态规划的方法。我们发现,序号5可以抵到终点,所以我们可以往前找,只要有序号能抵达序号5,就能顺藤摸瓜达到终点。所以序号1可以达到5,之后序号0可以达到序号1,因而必有一条路径直通终点!根据这个思路,我们总结出下面的步骤:

  1. 初始化TargetIdx = 终点序号,从后往前遍历,如果有nums[i] >= targetIdx - i,则有点必然达到TargetIdx;
  2. 更新TargetIdx = i,继续向前寻找;
  3. 结束时如果TargetIdx == 0,说明能从起点序号到终点序号,返回TRUE;反之,返回False;

4.2 实例代码

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int targetIdx = nums.size() - 1;
        for (int i = targetIdx - 1; i >= 0; i--) {
        	if (nums[i] >= targetIdx - i) { 
        		targetIdx = i;
        	}
        }
        
        return targetIdx == 0;
    }
};

5. 参考资料

  1. 55. Jump Game

在这里插入图片描述

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
跳跃游戏是一个贪心算法问题。在这个问题中,我们需要判断是否能够从数组的第0个位置跳跃到数组的最后一个位置。 我们可以使用贪心算法来解决这个问题。我们从第0个位置开始,一直跳到最后一个位置,每次选择能够跳跃最远的位置作为下一个跳跃点。我们使用一个变量max_index来记录当前能够跳到的最远位置。 具体步骤如下: 1. 创建一个空数组index,用于存储每个位置能够跳到的最远位置。 2. 遍历给定的数组nums,计算每个位置能够跳到的最远位置,并将其存入index数组。 3. 初始化变量jump为0,表示当前所在的位置。 4. 初始化变量max_index为index,表示当前能够跳到的最远位置。 5. 使用while循环,当jump小于index数组的大小且jump小于等于max_index时,执行循环体。 6. 在循环体中,如果max_index小于index[jump],则更新max_index为index[jump],表示当前能够跳得更远。 7. 每次循环结束后,将jump自增1。 8. 在循环结束后,判断jump是否等于index数组的大小,如果等于,则表示能够跳到最后一个位置,返回true,否则返回false。 代码如下所示: ```cpp bool CanJump(std::vector<int>& nums) { std::vector<int> index; for (unsigned int i = 0; i < nums.size(); i++) { index.push_back(i + nums[i]); } unsigned int jump = 0; int max_index = index[0]; while (jump < index.size() && jump <= max_index) { if (max_index < index[jump]) { max_index = index[jump]; } jump++; } if (jump == index.size()) { return true; } return false; } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值