Leetcode【贪心】| 55. 跳跃游戏

本文解析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];
    }
}

运行结果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用户

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值