前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【面试高频系列】修改数据范围,可以从「简单 BFS」变为「挖掘性质」的贪心 DP 题

【面试高频系列】修改数据范围,可以从「简单 BFS」变为「挖掘性质」的贪心 DP 题

作者头像
ACM算法日常
发布2021-06-16 16:03:22
4200
发布2021-06-16 16:03:22
举报
文章被收录于专栏:ACM算法日常

题目描述

这是 LeetCode 上的「45. 跳跃游戏 II」,难度为 Medium

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

假设你总是可以到达数组的最后一个位置。

示例 1:

代码语言:javascript
复制
输入: [2,3,1,1,4]

输出: 2

解释: 跳到最后一个位置的最小跳跃数是 2。
     
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:

代码语言:javascript
复制
输入: [2,3,0,1,4]

输出: 2

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <=
10^5

BFS

对于这一类问题,我们一般都是使用 BFS 进行求解。

本题的 BFS 解法的复杂度是

O(n^2)

,数据范围为

10^3

,可以过。

代码:

代码语言:javascript
复制
class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        int ans = 0;
        boolean[] st = new boolean[n];
        Deque<Integer> d = new ArrayDeque<>();
        st[0] = true;
        d.addLast(0);
        while (!d.isEmpty()) {
            int size = d.size();
            while (size-- > 0) {
                int idx = d.pollFirst();
                if (idx == n - 1) return ans;
                for (int i = idx + 1; i <= idx + nums[idx] && i < n; i++) {
                    if (!st[i]) {
                        st[i] = true;
                        d.addLast(i);
                    }
                }
            }
            ans++;
        }
        return ans;
    }
}
  • 时间复杂度:如果每个点跳跃的距离足够长的话,每次都会将当前点「后面的所有点」进行循环入队操作(由于 st 的存在,不一定都能入队,但是每个点都需要被循环一下)。复杂度为
O(n^2)
  • 空间复杂度:队列中最多有
n - 1

个元素。复杂度为

O(n)

双指针 + 贪心 + 动态规划

本题数据范围只有

10^3

,所以

O(n^2)

勉强能过。

如果面试官要将数据范围出到

10^6

,又该如何求解呢?

我们需要考虑

O(n)

的做法。

其实通过

10^6

这个数据范围,就已经可以大概猜到是道 DP 题。

我们定义

f[i]

为到达第

i

个位置所需要的最少步数,那么答案是

f[n - 1]

学习过 路径 DP 专题 的同学应该知道,通常确定 DP 的「状态定义」有两种方法。

  • 一种是根据经验猜一个状态定义,会结合题目给定的维度,和要求的答案去猜。
  • 另外一种则是通过设计一个合理的 DFS 方法签名来确定状态定义。

这里我是采用第一种方法。

至于如何确定「状态定义」是否可靠,关键是看使用这个状态定义能否推导出合理的「状态转移方程」,来覆盖我们所有的状态。

不失一般性的考虑

f[n - 1]

该如何转移:

我们知道最后一个点前面可能会有很多个点能够一步到达最后一个点。

也就是有

f[n - 1] = min(f[n - k],...,f[n - 3],f[n - 2]) + 1

然后我们再来考虑集合

f[n - k],...,f[n - 3],f[n - 2]

有何特性。

不然发现其实必然有

f[n - k] <= ...<= f[n - 3] <= f[n - 2]

推而广之,不止是经过一步能够到达最后一个点的集合,其实任意连续的区间都有这个性质。

举个?,比如我经过至少 5 步到达第

i

个点,那么必然不可能出现使用步数少于 5 步就能达到第

i + 1

个点的情况。到达第

i + 1

个点的至少步数必然是 5 步或者 6 步。

搞清楚性质之后,再回头看我们的状态定义:

f[i]

为到达第

i

个位置所需要的最少步数。

因此当我们要求某一个

f[i]

的时候,我们需要找到最早能够经过一步到达

i

点的

j

点。

即有状态转移方程:

f[i] = f[j] + 1

也就是我们每次都贪心的取离

i

点最远的点

j

来更新

f[i]

而这个找

j

的过程可以使用双指针来找。

因此这个思路其实是一个「双指针 + 贪心 + 动态规划」的一个解法。

代码:

代码语言:javascript
复制
class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        int[] f = new int[n]; 
        for (int i = 1, j = 0; i < n; i++) {
            while (j + nums[j] < i) j++;
            f[i] = f[j] + 1;
        }
        return f[n - 1];
    }
}
  • 时间复杂度:
O(n)
  • 空间复杂度:
O(n)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.45 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ACM算法日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目描述
  • BFS
  • 双指针 + 贪心 + 动态规划
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档