首页 > 试题广场 >

跳跃游戏(三)

[编程题]跳跃游戏(三)
  • 热度指数:4163 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
给定一个非负整数数组nums,假定最开始处于下标为0的位置,数组里面的每个元素代表下一跳能够跳跃的最大长度。请你判断最少跳几次能跳到数组最后一个位置。
1.如果跳不到数组最后一个位置或者无法跳跃(即数组长度为0),请返回-1
2.数据保证返回的结果不会超过整形范围,即不会超过


数据范围:


示例1

输入

[2,1,3,3,0,0,100]

输出

3

说明

首先位于nums[0]=2,然后可以跳2步,到nums[2]=3的位置,step=1
再跳到nums[3]=3的位置,step=2
再直接跳到nums[6]=100,可以跳到最后,step=3,返回3 
示例2

输入

[2,1,3,2,0,0,100]

输出

-1
动态规划和贪心算法的总体思路大致一致,都是先初始化第一个位置能够达到的最远位置 nums[0], 然后在 0~nums[0] 之间搜寻更优的解,如果找到,就更新最优解,然后继续计算,每次达到最远距离就计步器+1, 差别在于贪心算法仅使用一次循环,就可以更新出最远距离,而动态规划则必须以第一个台阶为基础,更新剩余的台阶,找到以第一个台阶为前提得情况下的最优解,然后再继续从下一个台阶往下找最优解,直到循环结束,便找到了全局最优解,这种做法与直接对步长进行逐一遍历(回溯)再找到全局最优解相比,省去了大量的重复计算,是典型的空间换时间,使用回溯算法仍然可以用记忆化搜索省去中间过程,但仍然避免不了大量的栈内存开销

public static int minJumpStep(int[] nums) {
        // 大致思路如下:
        // 1.初始化:从第0个台阶开始跳,最远可直接跳到 0 + nums[0] 的位置,
        // 2. 在跳到 nums[0] 之间, 再记录下来 nums[i] + i 的位置,它为最近连续两次跳过的最远位置
        // 3. 如果发现 0 ~ nums[0] 之间,有更优的原则,即连续跳跃两次距离更远,就使用这个更远的距离作为下一次跳跃的开始位置
        //    这是因为 0 ~ nums[0] 之间的任何一个台阶都可以一步到位,所以只需要看两次跳跃的最终距离: nums[i] + i
        // 4. 更新了最大跳跃的下标后,继续循环,循环过程中又继续找最大距离,直到满足最后一个元素.

        // 总结: 第一次初始化的时候选择第一个元素,在遍历过程中调整最优路径,确认最终位置,使得每次更新最终位置都是最优解,遍历结束则是全局最优解
        int n = nums.length;
        if (n == 0) return -1;
        if (n == 1) return 0;

        int maxReach = 0; // 当前能够到达的最远位置
        int steps = 0; // 当前步数,初始化,第一次循环为初始为1
        int currEnd = 0; // 当前步数下,能够到达的最远位置

        for (int i = 0; i < n - 1; i++) {
            maxReach = Math.max(maxReach, i + nums[i]);
            if (i == currEnd) {
                steps++;
                currEnd = maxReach;
            }
            if (currEnd >= n - 1) return steps;
        }

        return currEnd >= n - 1 ? steps : -1;
    }

    public static int minJumpStepDP(int[] nums) {
        if (nums.length == 0)
            return -1;
        int[] dp = new int[nums.length];
        int maxIndex = 0;
        // 从第0个台阶开始尝试
        for (int i = 0; i < nums.length && i <= maxIndex; ++i) {
            // 更新当前 i 的情况下最远能跳到的位置
            maxIndex = Math.max(maxIndex, i + nums[i]);
            // (i+1, i+nums[i]) 之间找最优解
            for (int j = i + 1; j <= i + nums[i] && j < nums.length; ++j) {

                // 初始化
                if (dp[j] == 0)
                    // 当 i > 0 , dp[i] 已经在之前的 dp[j] 中计算好了最优解
                    dp[j] = dp[i] + 1;
                else
                    // 因为 i ~ i+ nums[i] 是一步到位,因此加1
                    dp[j] = Math.min(dp[i] + 1, dp[j]);
                if (j == nums.length - 1)
                    return dp[j];
            }
        }
        return maxIndex >= nums.length - 1 ? dp[nums.length - 1] : -1;//首先判断是否能到达最后,能到达则返回最小步数,不能到达就返回-1.
    }



发表于 2024-04-30 16:44:14 回复(0)

暴力递归

从左往右尝试,每次的下一步都尝试走1~nums[i]步,继续深搜。如果当前位置cur大于等于n-1,表示已经到达了终点,更新最小步数;如果当前位置nums[cur]=0,表示到达此位置后无法再前进一步,直接返回,本次深搜无效。
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @return int整型
     */
    int minSteps = Integer.MAX_VALUE;
    public int minJumpStep (int[] nums) {
        // write code here
        int n = nums.length;
        if(n == 0) return -1;
        if(n == 1) return 0;
        dfs(nums, 0, 0);
        return minSteps == Integer.MAX_VALUE? -1: minSteps;
    }
    
    private void dfs(int[] nums, int cur, int steps) {
        if(cur >= nums.length - 1){
            minSteps = Math.min(minSteps, steps);
            return;
        }
        if(nums[cur] == 0){
            return;
        }
        for(int i = 1; i <= nums[cur]; i++){
            dfs(nums, cur + i, steps + 1);
        }
    }
}
暴力递归还是暴力了点,会引发StackOverflowError的栈溢出错误。但无疑为我们的动态规划铺好了道路。

动态规划

根据递归的逻辑,就能改出一版动态规划的解法,其中的状态转移方程
                  dp[cur]=Math.min(dp[cur],dp[cur+i]+1)
就相当于递归中30~32行的遍历部分,dp[cur+i]即为累积的步数steps。
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @return int整型
     */
    public int minJumpStep (int[] nums) {
        // write code here
        int n = nums.length;
        if(n == 0) return -1;
        if(n == 1) return 0;
        int[] dp = new int[n];     // dp[i]表示从i位置到最后一个位置需要的最少步数
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[n - 1] = 0;
        for(int cur = n - 2; cur >= 0; cur--){
            for(int i = 1; i <= nums[cur]; i++){
                if(cur + i >= n - 1){
                    // 从cur可以一步到达最后一个位置
                    dp[cur] = 1;
                    break;
                }else{
                    if(dp[cur + i] != Integer.MAX_VALUE){
                        // 在dp[cur+i]能到达最后位置的基础上再加上一步
                        dp[cur] = Math.min(dp[cur], dp[cur + i] + 1);
                    }
                }
            }
        }
        return dp[0] == Integer.MAX_VALUE? -1: dp[0];
    }
}

贪心算法

除此之外,还可以根据业务改出一版贪心的解法。我看大家通过的代码很多都是这个贪心思路,因此不再赘述。重点展示一个从暴力递归到动态规划的思路。
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @return int整型
     */
    public int minJumpStep (int[] nums) {
        // write code here
        int n = nums.length;
        if(n == 0) return -1;
        if(n == 1) return 0;
        int steps = 0, cur = 0, reach = 0;    // 步数,当前位置,可以到达的最远位置
        for(int i = 0; i < n - 1; i++){
            reach = Math.max(reach, i + nums[i]);     // 可以到达的最远位置
            if(i >= reach) return -1;
            if(cur >= n - 1) break;     // 已经可以到达最后一个位置
            if(i == cur){
                steps++;
                cur = reach;
            }
        }
        return steps;
    }
}

编辑于 2021-12-22 15:29:54 回复(3)
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @return int整型
     */
    public int minJumpStep(int[] nums) {
        // write code here
        if (nums.length == 0) {
            return -1;
        }
        if (nums.length == 1) {
            return 0;
        }

        int length = nums.length;
        int[] indexs = new int[length];
        int[] minx = new int[length];
        Arrays.fill(indexs, -1);
        for (int i = 0; i < length; i++) {
            int min = 0;
            //当前节点最短
            if (indexs[i] != -1) {
                min = minx[indexs[i]] + 1;
            }
            minx[i] = min;
            //覆盖index
            if (nums[i] > 0&&(min>0||i==0)) {
                for (int j = i+1; j <=Math.min(i + nums[i], length-1); j++) {
                    //判断最短路径
                    if (indexs[j] == -1||(indexs[j] != -1&&min<minx[indexs[j]]+1)) {
                        indexs[j] = i;
                    }
                }
                if (minx[length - 1] > 0) {
                    return minx[length - 1];
                }
            }
        }
        return minx[length - 1] > 0 ? minx[length - 1] : -1;
    }
    
}
发表于 2021-11-25 17:48:42 回复(0)