【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,但是還沒開始學,所以先不用。次好的方法用的思想就是我這個了。一步一步找到答案的感覺還是很不錯的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章