【LeetCode】45. Jump Game II 跳台阶 II

一、概述

广为人知的跳台阶问题。

输入一个序列,从第一个开始跳,每次跳的最远距离是该元素的值,问最少跳多少次出队列。

这题目,本质上是将具体的跳台阶问题化为抽象的贪心问题。

我是在BFS→剪枝→找最大值→得到较优解中一步步理解的。

二、分析

我最终版本的代码和最优代码思想是一致的,但是我懒得从BFS改成循环了。就只分析我自己的代码,还可以从中看出我的分析过程。

第一眼看到这个题,我开始想到贪心,怎么贪心呢?先把最大值找出来,然后找次大值。。。。。。这样,发现不成,比如说最大值在最后一个,就几乎不可能用到这个值;再之后找“元素下标加元素值”的最大值,发现也不成,比如全是1,怎么找,就得一个一个遍历。

于是想到了BFS。

BFS是个什么思想呢?建树,以序列2、3、1、1、4为例,如下是建出来的树:

红色为跳跃值,蓝色为下标。最开始从0起步,下标为0的元素值为2,则可以跳1步或2步,因此有两个子树,分别跳到1和2;1的值为3,因此有三棵子树,2的值为1,只有一棵子树......最后当下标加元素值大于等于序列长-1的时候就找到了结果。结果为树的层数。

所以最开始我的代码就是传统的BFS:

class Solution {
    int jump_next(vector<int>& nums,int nowLoc,int target)
    {
        queue<int> q;
        q.push(nowLoc);
        int nowjumps=1;
        int last=1;//当前层最后一个元素所在位置
        int cnp=1;//进入队列的节点总数
        int cmp=0;//当前位置
        while(!q.empty())
        {
            int tmp=q.front();
            q.pop();
            cmp++;
            if(tmp+nums[tmp]>=target)
                return nowjumps;
            for(int i=nums[tmp];i>0;i--)
            {
                q.push(i+tmp);
                cnp++;
            }
            if(cmp==last)
            {
                last=cnp;
                nowjumps++;
            }
        }
        return -1;
    }
public:
    int jump(vector<int>& nums) {
        if(nums.size()==1)
            return 0;
        int res=jump_next(nums,0,nums.size()-1);
        return res;
    }
};

由于需要记录当前层数,所以需要维护多个变量,注意BFS中记录当前层数的做法:

维护三个变量:last、cnp、cmp。

last是当前层的最后一个节点的位置。cnp是当前入队元素的总个数。cmp是当前出队的是第几个元素。

第m层的第cmp个元素出队时,它的叶子节点全部入队,cnp随之增加,当cmp是m层最后一个元素时,也就是cmp等于当前last的值,cnp的值就是last应该更新的值,此时层数加一。

上面代码很容易看懂,就是把所有叶子节点全部入队,没有任何剪枝操作。很明显的,超时了。

然后我就开始思考具体的剪枝。

跳台阶本质上是什么呢?

我把它想象成是一个帝国,每跳一次台阶,帝国的势力就变大一点。

对,每跳一次台阶,帝国的势力变大一点。

也就是说,如果该次台阶不能让帝国的势力变大,那还不如不跳。

仍然以上面那幅图举例:

我们可以发现,在进行第一次跳跃时,帝国有两种选择:我这一步跳到1,下一步帝国势力最远跳到4;这一步跳到2,下一步帝国势力最远跳到3。可能有些人会担心这样一种情况:虽然跳到2看起来下一步跳的少,但是可能存在这样一条路径,选2比选1更好。

不存在的,因为跳到2之后的可选项(3)是跳到1后的可选项(2,3,4)的子集,所以跳到1是完全比2好的。也就是说,帝国做的选择取决于该选择的潜力,潜力如何量化呢?下标*元素值。

再来看1的三棵子树,分别能把帝国势力扩大到2,3,4,意思就是我到1了之后,能把帝国的范围最多扩大到4。然后看2,3,4三个节点,它们都只有一棵子树,分别能到3,4,5,也就是说,如果我下一步跳到4,那么我之后最远能到5,如果我跳到3,之后我最远能跳到4,如果我调到2,之后我最远能跳到3,即2,3,4的潜力分别为3,4,5,选择跳到潜力最大的4。

这样就找到最优路径了。也就是说,每层只要把“潜力”最大的入队即可。

优化后的代码如下:

class Solution {
    int jump_next(vector<int>& nums,int nowLoc,int target)
    {
        queue<int> q;
        q.push(nowLoc);
        int nowjumps=1;
        int last=1;//当前层最后一个元素所在位置
        int cnp=1;//进入队列的节点总数
        int cmp=0;//当前位置
        while(!q.empty())
        {
            int tmp=q.front();
            q.pop();
            ++cmp;
            if(tmp+nums[tmp]>=target)
                return nowjumps;
            int max=tmp;//潜力初值
            for(int i=nums[tmp];i>0;--i)
                if(i+tmp+nums[i+tmp]>max+nums[max])//潜力比较
                    max=i+tmp;//潜力更新
            q.push(max);//潜力最大的入队
            ++cnp;
            if(cmp==last)
            {
                last=cnp;
                ++nowjumps;
            }
        }
        return -1;
    }
public:
    int jump(vector<int>& nums) {
        if(nums.size()==1)
            return 0;
        int res=jump_next(nums,0,nums.size()-1);
        return res;
    }
};

这样就把所有的无用枝都减掉了。

三、总结

最好的做法是DP,但是还没开始学,所以先不用。次好的方法用的思想就是我这个了。一步一步找到答案的感觉还是很不错的。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值