给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
根据leetcode官方给的题解可以知道这是一个动态规划的问题,一个动态规划问题需要以下四个步骤:
1.利用递归回溯解决问题
2.利用记忆表优化(自顶向下的动态规划)
3.移除递归的部分(自底向上的动态规划)
4.使用技巧减少时间和空间复杂度
方法1:回溯
这个方法低效,模拟从第一个位置跳到最后得所有方案。从第一个位置开始,模拟所有可以跳到的位置,然后再从当前位置重复上一个动作,当没有办法跳的时候,就回溯。
直觉上选择最大的步数去跳跃可以更快的到达终点
方法2:自顶向下的动态规划
可以理解为回溯法的优化,当确定一个坐标是好坐标或者坏坐标后,就不会改变了,这意味着我们可以记录这个结果,每次不用重复计算
方法3:自底向上的动态规划
这个方法有更好的时间效率,因为我们不需要栈空间,可以节省很多缓存开销。这让以后有更好的优化空间。回溯通常是通过反转动态规划的步骤来实现的
我们每次只会向右移动,意味着如果从右边开始动态规划,每次查询右边结点的信息,都是已经计算过的,不再需要额外的递归开销
enum Index {
GOOD, BAD, UNKNOWN
}
public class Solution {
public boolean canJump(int[] nums) {
Index[] memo = new Index[nums.length];
for (int i = 0; i < memo.length; i++) {
memo[i] = Index.UNKNOWN;
}
memo[memo.length - 1] = Index.GOOD;
for (int i = nums.length - 2; i >= 0; i--) {
int furthestJump = Math.min(i + nums[i], nums.length - 1);
for (int j = i + 1; j <= furthestJump; j++) {
if (memo[j] == Index.GOOD) {
memo[i] = Index.GOOD;
break;
}
}
}
return memo[0] == Index.GOOD;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/jump-game/solution/tiao-yue-you-xi-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
方法4:贪心
当把代码改成自底向上的模式,可以看出从某个位置出发,只需要找到第一个标记为good的坐标,也就是找到最左边的那个坐标。如果用一个单独的变量来记录最左边的good位置,一直重复到数组的开头,如果第一个坐标标记为good意味着可以从第一个位置跳到最后的位置
public class Solution {
public boolean canJump(int[] nums) {
int lastPos = nums.length - 1;
for (int i = nums.length - 1; i >= 0; i--) {
if (i + nums[i] >= lastPos) {
lastPos = i;
}
}
return lastPos == 0;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/jump-game/solution/tiao-yue-you-xi-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
贪心最优解百分之百
下面也是在题解中看到,更通俗易懂
解法三
让我们直击问题的本质,与 45 题不同,我们并不需要知道最小的步数,所以我们对跳的过程并不感兴趣。并且如果数组里边没有 0,那么无论怎么跳,一定可以从第 0 个跳到最后一个位置。
所以我们只需要看 0 的位置,如果有 0 的话,我们只需要看 0 前边的位置,能不能跳过当前的 0 ,如果 0 前边的位置都不能跳过当前 0,那么直接返回 false。如果能的话,就看后边的 0 的情况。
public boolean canJump(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
//找到 0 的位置
if (nums[i] == 0) {
int j = i - 1;
boolean isCanSkipZero = false;
while (j >= 0) {
//判断 0 前边的元素能否跳过 0
if (j + nums[j] > i) {
isCanSkipZero = true;
break;
}
j--;
}
if (!isCanSkipZero) {
return false;
}
}
}
return true;
}
但这样时间复杂度没有提高, 在 @Zhengwen 的提醒下,可以用下边的方法。
我们判断 0 前边的元素能否跳过 0 ,不需要每次都向前查找,我们只需要用一个变量保存当前能跳的最远的距离,然后判断最远距离和当前 0 的位置就可以了。
public boolean canJump(int[] nums) {
int max = 0;
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i] == 0 && i >= max) {
return false;
}
max = Math.max(max, nums[i] + i);
}
return true;
}
时间复杂度:O(n)。
空间复杂度:O(1)。
我们甚至不需要考虑 0 的位置,只需要判断最大距离有没有超过当前的 i 。
public boolean canJump(int[] nums) {
int max = 0;
for (int i = 0; i < nums.length; i++) {
if (i > max) {
return false;
}
max = Math.max(max, nums[i] + i);
}
return true;
}
作者:windliang
链接:https://leetcode-cn.com/problems/jump-game/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by--17/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。