Leetcode【贪心】| 55. 跳跃游戏
题目
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game
解题
思路
从第一个位置开始,得到它能到达的最远的距离第i+nums[i]个点,即if canEnter[i] = true,则canEnter[i+nums[i]]。而这种中间的所有连续的点也都能到达,如i+1,i+2到i+nums[i]。依次遍历,维护能到达的最远的点。当最远的点>=n-1时,则说明能到达最后一个位置,如果最远的点始终到不了最后一个位置,则说明到达不了最后一个位置,返回false。
这种思想其实就是贪心算法的思想。一直维护子结构的最优选择。该题的具体贪心策略就是:从起始点就开始,维护各个点能到达的最远的坐标。
java实现
class Solution {
public boolean canJump(int[] nums) {
int n = nums.length;
boolean[] canEnter = new boolean[n];
canEnter[0] = true;
//判断特殊情况
if(n == 1){
return true;
}
for(int i = 0; i < n-1; i++){//从起始点开始遍历
if(canEnter[i]==true && nums[i]>0){
if( i+nums[i] < n){//最远点不到最后位置
for(int j = 1; j <= nums[i]; j++){
canEnter[i+j] = true;//将当前点能到达的距离都标记为canEnter
}
}else{
canEnter[n-1] = true;
}
}else{
canEnter[n-1] = false || canEnter[n-1];//表明从第i点暂时无法到达最后位置
}
}
return canEnter[n-1];
}
}

其实这个代码写的好像有点BFS的意思? 其实刚开始用canEnter[]这个数组的时候是想用动态规划的,但是想着想着想不出来就想偏了…写的复杂了,时间和空间效率都不高,将其优化为贪心,优化代码如下:
优化
贪心解法(正着推)
将上述代码中的判断canEnter[i]==true与for循环将最远点左边的连续的点的canEnter都标记为true的复杂操作直接用 i <= rightmax一句给替代了,时间和空间上的消耗都节省了。
class Solution {
public boolean canJump(int[] nums) {
int n = nums.length;
int rightmax = 0;//维护能到达的最远的点的索引坐标
for(int i = 0; i < n; i++){
// if(i <= rightmax){
// rightmax = Math.max(rightmax,i+nums[i]);
// }
if(i > rightmax) break;//可提前结束循环更优化时间
rightmax = Math.max(rightmax,i+nums[i]);
}
// if(rightmax >= n-1){
// return true;
// }else{
// return false;
// }
return rightmax>=n-1;
}
}

或者用while循环写法
class Solution {
public boolean canJump(int[] nums) {
//贪心策略,遍历一遍数组,记录当前可到达的最远位置
if(nums == null || nums.length == 0) return false;
int farest = 0;
int i = 0;//初始位置
int n = nums.length;
while(i<=farest && i < n){
farest = Math.max(farest, nums[i]+i);
if(farest>=n-1) return true;
i++;
}
return farest>=n-1;
}
}

动态规划(正着递推)
class Solution {
public boolean canJump(int[] nums) {
int n = nums.length;
boolean[] dp = new boolean[n];//dp = canEnter
dp[0] = true;
for(int i = 1; i < nums.length; i++){
for(int j = 0; j < i; j++){//遍历i左边的坐标
if(dp[j] && nums[j] >= i-j){//其实这个就是 如果i能到达,i+nums[i]以内的也能到达 的反过来:如果j能到达,i在j+nums[j]以内,i也能到达
dp[i] = true;
}
}
}
return dp[n-1];
}
}

倒着推
从最后一个坐标开始,假设前一个坐标能到达,通过前一个坐标的值nums[i-1]判断第i点能否到达。而满足前一个坐标到达的条件,是前前个坐标的值…如此一直递推,看能否一直推到第一个坐标,而第一个坐标能到达,表明之后的都能满足一直到最后一个坐标。
class Solution {
public boolean canJump(int[] nums) {
if (nums == null || nums.length == 0) {
return false;
}
//pos表示需要到达的位置
int pos = nums.length - 1;
for (int i = nums.length - 2; i >= 0; i--) {
if (nums[i] + i >= pos) {
pos = i;
}
}
return pos == 0;//表明最终推到第一个坐标,而第一个坐标初始条件就能到达。
}
}

另一种思路(判断特殊情况:0点是否能越过 用到队列)
跳不到最后的情形:存在越不过0的情况
思路:依次找到所有0,放入队列,挨个判断0点能否越过。能越过条件:该0点之前存在一个i点的值nums[i] > i到该0点距离,特殊情况最后一个值为0时, 之前存在一个点i的nums[i] >= i 到该0点距离都可。
循环结束标志:有一个0点越不过,或者全部越过
class Solution {
public boolean canJump(int[] nums) {
int n = nums.length;
Queue<Integer> queue = new LinkedList<>();
boolean res = true;
for(int i = 0;i<n;i++){
if(nums[i] == 0 && n != 1){
queue.offer(i); //注意{0}的情况,将值为0的索引都插入队列中
}
}
//判断0是否能越过
while(!queue.isEmpty()){
int temp = queue.poll(); //用队列的不断弹出来循环
boolean x = false;
for(int i = 0;i<temp;i++){
//当前0点不是最后一个元素时,temp(0点)之前存在一个i的nums[i]+i>temp
//当前0点时最后一个元素时,可以不止越过,到达就行了 所以时nums[i]+i >=temp
if((temp != n-1 && nums[i]>temp-i) || (temp == n-1 && nums[i]>=temp-i)){
x = true;//表明第i个坐标的0可越过
break; //存在一个能越过当前0点的值
}
}
if(!x){//x为false时
res = false; //只要有一个0点越不过就跳出循环,返回false;
break;
}
}
return res;
}
}

进阶题目:Leetcode | 45. 跳跃游戏 II 求跳跃到终点所需最小步数
参考:
[1] 官方题解以及下面的评论
[2] 顺着推、倒着推两种方式,击败了99%的java用户
本文解析Leetcode 55.跳跃游戏题目,介绍多种解题思路包括贪心算法、动态规划及倒序推导等,详细阐述每种方法的实现过程与优化策略。
404

被折叠的 条评论
为什么被折叠?



