leetcode 55. 跳跃游戏 思考分析

167 阅读3分钟

题目

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

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

判断你是否能够到达最后一个位置。

示例1:

输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

示例2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

思考

这个题目,需要注意每个元素代表你在该位置可以跳跃的最大长度,而不是长度。也就是说如果是3,那么你可以走0,1,2,3步。
其次,观察题目,不能到达最后一格的原因是:某一格元素为0,且之前没有足够步数跨越这个0
来分析一下示例2:[3,2,1,0,4],连续的0个数为1
我们先遍历,找到元素0,下标为3。此刻从它前面的一个元素开始往前遍历:
下标为2的元素为1,步数为1,(步数 - (连续0的第一个下标 - 当前下标 - 1)) =(1 - (3-2 -1))=1 <= 连续的0个数,所以不行
下标为1的元素为2,步数为2,(步数 - (连续0的第一个下标 - 当前下标 - 1)) =(2 - (3-1 -1))=1 <= 连续的0个数,所以不行
下标为0的元素为3,步数为3,(步数 - (连续0的第一个下标 - 当前下标 - 1)) =(3 - (3-0 -1))=1 <= 连续的0个数,所以不行

所以我们可以归纳出一个明显的特征:(步数 - (连续0的第一个下标 - 当前下标 - 1))> 连续的0个数
只有满足这个特征才算是可以到达最后一个位置。
代码实现的时候还需要考虑特殊情况以及细节:
1、长度为1,直接为true
2、遍历从 i=1开始,i= nums.size()-1,结束
3、获取连续0的个数:

int succesive_zero = 0;
if(nums[i] == 0)
{
    succesive_zero = 1;
    for(int j = i;j < nums.size()-2;j++)
    {
        if(nums[j] == 0 && nums[j+1] == 0) succesive_zero++;
        else break;
    }
}

4、如果找到了可以可以跨越连续0的步数,那么将i指针移动到跳跃的格子。并且,如果在连续0之前没有找到足够大的步数跳跃,则说明不能到达最后一个格子,但是找到了,还需要遍历之后的情况,直到整个数组都被遍历了,且没有出现不能跳过的情况,我们才能返回true。

int flag = 0;
for(int j = i-1;j >= 0;j--)
{
    if((nums[j]-(i-j-1)) > succesive_zero)
    {
        i=i+succesive_zero;
        flag=1;
        break;
    }
}
if(flag == 0) return false;

AC代码

class Solution {
public:
    bool canJump(vector<int>& nums) {
        if(nums.size() == 1) return true;
        if(nums[0] == 0) return false;
        for(int i=1;i < nums.size()-1;i++)
        {
            //如果当前为0,记录连续0的个数
            int succesive_zero = 0;
            if(nums[i] == 0)
            {
                succesive_zero = 1;
                for(int j = i;j < nums.size()-2;j++)
                {
                    if(nums[j] == 0 && nums[j+1] == 0) succesive_zero++;
                    else break;
                }
                //cout<< "succesive_zero"<<succesive_zero << endl;
                //判断i之前有没有步数,在走到i后还有剩余的步数跳过这个0,说明暂时可以,否则不行
                int flag = 0;
                for(int j = i-1;j >= 0;j--)
                {
                    if((nums[j]-(i-j-1)) > succesive_zero)
                    {
                        i=i+succesive_zero;
                        flag=1;
                        break;
                    }
                }
                if(flag == 0) return false;
            }
        }
        return true;
    }
};

在这里插入图片描述

参考,贪心思路,思路更加直观,代码逻辑更加简单

参考代码随想录的思路:mp.weixin.qq.com/s/606_N9j8A…
跳几步无所谓,关键在于可跳的范围:
每次取最大的跳跃步数,这个就是跳跃的覆盖范围。
问题转化为跳跃覆盖范围究竟可不可以覆盖到终点。
每次移动取最大跳跃步数(得到最大覆盖范围),每移动一个单位,就更新最大覆盖范围。

**局部最优解:**每次取最大跳跃步数
**整体最优解:**最后得到整体最大覆盖范围,看是否能到终点。
在这里插入图片描述
编程细节:
1、i只能在cover范围内移动,每移动一个元素,cover得到该元素数值,补充覆盖范围,让i继续移动下去。
2、cover每次只取max(钙元素数值补充后的范围,cover本身范围)
3、如果cover >= 终点下标,直接return true;
AC代码:

class Solution {
public:
    bool canJump(vector<int>& nums) {
        //初始化覆盖范围
        int cover = 0;
        if(nums.size() == 1) return true;
        //从0开始的覆盖范围迭代
        for(int i = 0; i <= cover;i++)
        {
            cover = max(i+nums[i],cover);
            if(cover >= nums.size() - 1) return true; 
        }
        return false;
    }
};

有一说一,这个思路确实妙!也很符合贪心的逻辑。