[Leetcode刷题] - LC 2770 达到末尾下标所需的最大跳跃次数

题目描述

给你一个下标从 0 开始、由 n 个整数组成的数组 nums 和一个整数 target 。你的初始位置在下标 0 。

在一步操作中,你可以从下标 i 跳跃到任意满足下述条件的下标 j:

  • 0 <= i < j < n
  • -target <= nums[j] - nums[i] <= target

返回到达下标 n - 1 处所需的最大跳跃次数,如果无法到达下标 n - 1 ,返回 -1 。

题目链接

LC 2770 Maximum Number of Jumps to Reach the Last Index

题目思路

这道题对于理解单序列DP 如何从DFS 到记忆化搜索 再到 动态规划递推有很大帮助, 所以我也把我整个思考过程思路全部放在里边。

1. 暴力DFS - 使用全局变量保存最大步数 - 超时

这道题目是跳跃游戏的改进版,思路和跳跃游戏一致,最简单的思路就是利用DFS暴力枚举所有情况,每一次递归中,遍历后继点,当后继点和当前点差值绝对值<target 则进行下一层递归,如果达到终点,则计算最大步数并且更新全局最大值。

class Solution {
    int[] n;
    int tgt;
    int max = -1;
    public int maximumJumps(int[] nums, int target) {
        // dfs
        n = nums;
        tgt = target;

        dfs(0, 0);
        return max;

    }

    private void dfs(int idx, int step) {
        if (idx==n.length-1) {
            max = Math.max(max, step);
        }

        for(int i=idx+1; i<n.length; i++) {
            if (Math.abs(n[i]-n[idx])<=tgt) {
                dfs(i, step+1);
            }
        }
    }
}

时间复杂度:O(N^k), 递归每一步都要还要遍历能走的最大步数k;空间复杂度:O(1), 如果不考虑额外开的栈空间。

2. 暴力DFS - 不设置全局变量存储最大步数

想要后续理解如果进行记忆化搜索并且动态规划递推,我们首先需要理解如果实现不加入全局变量的情况下实现DFS暴力搜索。思路也很简单,当递归达到出口idx = nums.length; 我们直接返回0,每一层递归方程则可以写成:

if \left | nums[i] - nums[i+jump] \right | < target

                result = max(result, dfs(i+jump) + 1)

代码如下:

private int dfs(int idx) {
        if (idx==0) {
            return 0;
        }

        int res = Integer.MIN_VALUE;
        for (int i=idx-1; i>=0; i--) {
            if (Math.abs(n[i]-n[idx])<=tgt) {
                res = Math.max(res, dfs(i)+1);
            }
        }

        return res;
    }

3. DFS + 记忆化搜索

能否实现记忆化首先要确定解空间是否存在重复解,当你向后递归时,不同的两个点可能会跳到同一个节点,这时后续的计算就是重复的,所以我们加入单序列 dp用来存储top-down运算时出现的重复情况。

代码如下:

  1. 需要注意对于dp[] 初始化,定义为从i 到 末尾节点所用的最大步数,我们以-1来表示当前节点没有访问过,如果当前节点无法跳到末尾,那么我们就赋值 Integer.MAX_VALUE表示无法到达。
class Solution {
    int[] n;
    int tgt;
    int[] dp;
    int max = -1;
    public int maximumJumps(int[] nums, int target) {
        // dfs
        n = nums;
        tgt = target;

        dp = new int[n.length]; //i跳到ed最大步数
        Arrays.fill(dp, -1);
        dp[n.length-1] = 0;

        int ans = dfs(0);
        return ans<0? -1: ans;
    }
    /**
    
     */
    private int dfs(int idx) {
        if (idx==n.length-1) {
            return 0;
        }

        if (dp[idx]!=-1) return dp[idx]; // 算过了
        int res = Integer.MIN_VALUE;
        for(int i=idx+1; i<n.length; i++) {
            //遍历后面的点找到最大步数更新
            if(Math.abs(n[i]-n[idx])<=tgt) {
                res = Math.max(dfs(i)+1, res);
            }
        }

        return dp[idx] = res;
    }

}

时间复杂度:记忆化时间复杂度比较难估计;空间复杂度:O(N)

4. 动态规划递推

DFS + 记忆化搜索本身就是top-down的动态规划,那么如何将递归转化成递推表达式?整体思路是定义 单序列 dp[i] 为从0到 i 的最大步数,初始化为 -1 表示无法到达该点,当然这里你也可以定义为从 i 到末尾的最大步数,实现方式大同小异。

接下来就是定义递推状态转移方程,我们从左到右遍历每一个点,同时遍历该点可以达到的下一个点,

dp[i] != -1 : dp[i+jump] = Math.max(dp[i+jump], dp[i]+1)

dp[i]==-1: dp[i+jump] = -1

代码如下:

class Solution {
    public int maximumJumps(int[] nums, int target) {
        // DP Iteration
        int[] dp = new int[nums.length]; // 从0到i最大步数
        Arrays.fill(dp, -1);
        dp[0] = 0;

        for (int i=1; i<nums.length; i++) {
            // 外循环i从1 -> n
            for (int k=0; k<i; k++) {
                //内循环从[1, i]更新所有值
                if (Math.abs(nums[k]-nums[i])<=target && dp[k]!=-1) {
                    // k can jump to i and 0 can jump to k
                    dp[i] = Math.max(dp[i], dp[k]+1);
                }
            }
        }

        return dp[nums.length-1];
    }
}

时间复杂度:O(N^2);空间复杂度:O(N)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值