【LeetCode】跳跃游戏

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

e.g. 

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

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

 

我的想法是递归

方法一:

 1 bool canJump(vector<int>& nums) {
 2     return jump(nums, 0);
 3 }
 4     
 5 bool jump(vector<int> &nums, int m) {
 6     int last = nums.size() - 1;
 7     if (m == last)   return true;
 8     int p = min(last, m + nums[m]);
 9     for (int i = p; i > m; i--) {    // 若不定义 p,i 初始为 m + nums[m] 并不会造成数组越界,但变量 p 减少了不必要的递归
10         if (jump(nums, i))
11             return true;
12     }
13     return false;
14 }

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

 

答案提供了多种解法

方法二:

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

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

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

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

Index0123456
nums2421020
memoGGBBBGG

步骤:

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

C++实现:

 1 enum Index {
 2     GOOD, BAD, UNKNOWN
 3 };
 4 
 5 vector<Index> memo;
 6     
 7 bool canJump(vector<int>& nums) {
 8     memo.reserve(nums.size());
 9     for (int i = 0; i < nums.size() - 1; i++) {
10         memo[i] = UNKNOWN;
11     }
12     memo[nums.size() - 1] = GOOD;
13     return jump(nums, 0);
14 }
15     
16 bool jump(vector<int> &nums, int m) {
17     if (memo[m] != UNKNOWN)
18         return memo[m] == GOOD ? true : false;
19     int p = min((int)nums.size() - 1, m + nums[m]);
20     for (int i = p; i > m; i--) {
21         if (jump(nums, i)) {
22             memo[m] = GOOD;
23             return true;
24         }
25     }
26     memo[m] = BAD;
27     return false;
28 }

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

 

方法三:

自底向上动态规划法

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

C++实现:

 1 enum Index {
 2     GOOD, BAD, UNKNOWN
 3 };
 4 
 5 vector<Index> memo;
 6     
 7 bool canJump(vector<int>& nums) {
 8     memo.reserve(nums.size());
 9     for (int i = 0; i < nums.size() - 1; i++) {
10         memo[i] = UNKNOWN;
11     }
12     memo[nums.size() - 1] = GOOD;
13     
14     for (int i = nums.size() - 2; i >= 0; i--) {
15         int p = min((int)nums.size() - 1, i + nums[i]);
16         for (int j = p; j > i; j--) {
17             if (memo[j] == GOOD) {
18                 memo[i] = GOOD;
19                 break;
20             }
21         }
22     }
23     
24     return memo[0] == GOOD;
25 }

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

 

最优方法:

贪心法

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

C++实现:

1 bool canJump(vector<int>& nums) {
2     int last = nums.size() - 1;
3     for (int i = last; i >= 0; i--) {
4         if (i + nums[i] >= last)
5             last = i;
6     }
7     return last == 0;
8 }

 

也可以从前向后

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

转载于:https://www.cnblogs.com/wayne793377164/p/7268492.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值