给定一组非负整数,初始时处于数组的第一位下标 0 的位置,数组的每个元素代表那个位置可以跳跃的最大长度。判断你是否能够到达数组的最后一位下标。

e.g.

A = [2, 3, 1, 1, 4],返回 true。

A = [3, 2, 1, 0, 4],返回 false。

我的想法是递归

方法一:

 bool canJump(vector<int>& nums) {
return jump(nums, );
} bool jump(vector<int> &nums, int m) {
int last = nums.size() - ;
if (m == last) return true;
int p = min(last, m + nums[m]);
for (int i = p; i > m; i--) { // 若不定义 p,i 初始为 m + nums[m] 并不会造成数组越界,但变量 p 减少了不必要的递归
if (jump(nums, i))
return true;
}
return false;
}

答案称这种方法是递归回溯法,时间复杂度为 O(2n),数据量很大时会超时。

答案提供了多种解法

方法二:

自顶向下动态规划法(优化的回溯法)

作如下定义:如果从数组中某一位置作为起始点,最终能够到达最后一个下标处,则把这个位置称为 “ Good Index ”,否则成为 “ Bad Index ”。因此这个跳跃问题就变成了判断 0 下标是不是一个 “ Good Index ”。

用一个 memo 数组存储原数组每个下标是好的还是坏的,memo 数组元素的值是 GOOD、BAD、UNKNOWN 之一。

e.g. 对于 nums = [2, 4, 2, 1, 0, 2, 0],

Index 0 1 2 3 4 5 6
nums 2 4 2 1 0 2 0
memo G G B B B G G

步骤:

  1. 初始时,memo 中所有元素都是 UNKNOWN,除了最后一个元素是 GOOD。
  2. 将回溯法中递归的第一步改为 “ 判断当前下标是否 UNKNOWN ”
    如果是 KNOWN,根据其值是 GOOD / BAD 返回 true / false;
    否则继续执行回溯。
  3. 一旦知道当前下标是好的还是坏的,将相应的值存在 memo 数组中。

C++实现:

 enum Index {
GOOD, BAD, UNKNOWN
}; vector<Index> memo; bool canJump(vector<int>& nums) {
memo.reserve(nums.size());
for (int i = ; i < nums.size() - ; i++) {
memo[i] = UNKNOWN;
}
memo[nums.size() - ] = GOOD;
return jump(nums, );
} bool jump(vector<int> &nums, int m) {
if (memo[m] != UNKNOWN)
return memo[m] == GOOD ? true : false;
int p = min((int)nums.size() - , m + nums[m]);
for (int i = p; i > m; i--) {
if (jump(nums, i)) {
memo[m] = GOOD;
return true;
}
}
memo[m] = BAD;
return false;
}

对于数组中每个位置 i,我们在其右边 nums[i] 个元素中寻找 “ Good Index ”,因此这种方法的时间复杂度是 O(n2)。

方法三:

自底向上动态规划法

将自顶向下 DP 的递归消除后就变成了自底向上 DP,这样就不会造成方法栈的过度开销。消除递归的方法是从数组最右边(倒数第二位)往左不断地递推 memo 数组的值。

C++实现:

 enum Index {
GOOD, BAD, UNKNOWN
}; vector<Index> memo; bool canJump(vector<int>& nums) {
memo.reserve(nums.size());
for (int i = ; i < nums.size() - ; i++) {
memo[i] = UNKNOWN;
}
memo[nums.size() - ] = GOOD; for (int i = nums.size() - ; i >= ; i--) {
int p = min((int)nums.size() - , i + nums[i]);
for (int j = p; j > i; j--) {
if (memo[j] == GOOD) {
memo[i] = GOOD;
break;
}
}
} return memo[] == GOOD;
}

由于两种 DP 的原理相同,这种方法的时间复杂度也是 O(n2)。由于没有使用递归,终于 Accepted 了,但效率极低。

最优方法:

贪心法

自底向上 DP 中,从后向前递推 memo 数组时,对于每一个下标 i,我们想知道是否能从这个位置到达一个 “ Good Index ”,而当我们找到了从 i 下标能到达的下标范围 ( i, p ] 内最右边的仅仅一个 “ Good Index ”(也可以选择最左边的一个)就把 i 设为 “ Good Index ”。因此,可以把从后向前追踪的一系列 “ Good Index ” 只用一个单独的变量存储,而不是记录在 memo 数组中。

C++实现:

 bool canJump(vector<int>& nums) {
int last = nums.size() - ;
for (int i = last; i >= ; i--) {
if (i + nums[i] >= last)
last = i;
}
return last == ;
}

也可以从前向后

 bool canJump(vector<int>& nums) {
int n = nums.size(), reach = ; // reach 表示从 0 能到达的最远的下标
for (int i = ; i < n && i <= reach; i++) {
reach = max(reach, i + nums[i]);
if (reach >= n - )
return true;
}
return false;
}

【LeetCode】跳跃游戏的更多相关文章

  1. Leetcode 跳跃游戏 II

    题目链接:https://leetcode-cn.com/problems/jump-game-ii/ 题目大意: 略. 分析: 贪心 + DP. 代码如下: class Solution { pub ...

  2. LeetCode:跳跃游戏【55】

    LeetCode:跳跃游戏[55] 题目描述 给定一个非负整数数组,你最初位于数组的第一个位置.数组中的每个元素代表你在该位置可以跳跃的最大长度.判断你是否能够到达最后一个位置. 示例 1: 输入: ...

  3. [LeetCode] 45. Jump Game II 跳跃游戏 II

    Given an array of non-negative integers, you are initially positioned at the first index of the arra ...

  4. 力扣Leetcode 45. 跳跃游戏 II - 贪心思想

    这题是 55.跳跃游戏的升级版 力扣Leetcode 55. 跳跃游戏 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 你的目标是使用最少的跳跃 ...

  5. LeetCode 45跳跃游戏&46全排列

    原创公众号:bigsai,回复进群加入力扣打卡群. 昨日打卡:LeetCode 42字符串相乘&43通配符匹配 跳跃游戏 题目描述: 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中 ...

  6. 【LeetCode每天一题】Jump Game II(跳跃游戏II)

    Given an array of non-negative integers, you are initially positioned at the first index of the arra ...

  7. LeetCode(45): 跳跃游戏 II

    Hard! 题目描述: 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 你的目标是使用最少的跳跃次数到达数组的最后一个位置. 示例: 输入: [ ...

  8. [Leetcode]44.跳跃游戏Ⅰ&&45.跳跃游戏Ⅱ

    跳跃游戏链接 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 判断你是否能够到达最后一个位置. 示例 1: 输入: [2,3,1,1,4] 输出 ...

  9. Leetcode力扣45题 跳跃游戏 II

    原题目: 跳跃游戏 II 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 你的目标是使用最少的跳跃次数到达数组的最后一个位置. 示例: 输入: ...

  10. LeetCode 45. 跳跃游戏 II | Python

    45. 跳跃游戏 II 题目来源:https://leetcode-cn.com/problems/jump-game-ii 题目 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素 ...

随机推荐

  1. UVa 11107 生命的形式(不小于k个字符串中的最长子串)

    https://vjudge.net/problem/UVA-11107 题意:给定n个字符串,求出现在不小于n的一半个字符串的最长子串,如果有多个,则按字典序输出. 思路: 首先就是将这n个字符串连 ...

  2. 线性判别分析(Linear Discriminant Analysis-LDA)

    Linear Discriminant Analysis(LDA线性判别分析) 用途:数据预处理中的降维,分类任务 目标:LDA关心的是能够最大化类间区分度的坐标轴成分,将特征空间(数据集中的多维样本 ...

  3. 新加坡金融科技节 | 蚂蚁金服CTO程立:面向全球开放,与合作伙伴共赢

    小蚂蚁说: 11月13日,在新加坡金融科技节上,蚂蚁金服CTO程立分别从TechFin.BASIC战略.SOFAStack全栈分布式体系以及全面开放等方面讲述蚂蚁金融科技. TechFin是一种“倒立 ...

  4. Mybatis中sql语句中的in查询,判断null和size为0的情况

    不严谨的写法,可能会报错:in (),这种情况不符合SQL的语法,导致程序报错. 如果简单只做非空判断,这样也有可能会有问题:本来in一个空列表,应该是没有数据才对,却变成了获取全部数据! 所以一个比 ...

  5. ORA-03113: end-of-file on communication channel(归档满处理方法)

    归档放在flash_recovery目录,由于归档占满了闪回目录,数据库启动报错ORA-03113: end-of-file on communication channel tail -1200f ...

  6. JavaScript 局部刷新

    JavaScript局部刷新具体代码展示如下 1.  #tabList代表需要刷新的元素的对象 2.  第二个#tabList 如果后面有第三个元素,那么后面需要加>*符号,如果不加,容易造成C ...

  7. [osgearth][原]仿照谷歌,修改oe漫游器中focal(视角切换)功能

    oe中的视角加速感觉好奇怪,就仿照谷歌方式去改了. 先看看oe原来的漫游器改变视角的接口: void CameraManipulator::setViewpoint(const Viewpoint&a ...

  8. openModelica调试

    1打印信息 Modelica.Utilities.Streams.print(“messge”);

  9. 自动化测试基础-断言(Assert)使用方法

    junit中的assert方法全部放在Assert类中,总结一下junit类中assert方法的分类.1.assertTrue/False([String message,]boolean condi ...

  10. PowerDesign的简单使用方法

    PowerDesigner是一款功能非常强大的建模工具软件,足以与Rose比肩,同样是当今最著名的建模软件之一.Rose是专攻UML对象模型的建模工具,之后才向数据库建模发展,而PowerDesign ...