LeetCode45--Jump Game II

這一題思路其實挺明確的,有點類似跳臺階,就是怎麼從第一個最快的跑到最後一個元素去,剛開始大家容易被帶偏想到貪心算法,但是這一題貪心算法顯示行不通,你這次跳的遠,但是你到達的臺階可能並不能讓你跳的很遠,甚至是0直接跳不了,而你中間可能忽略了幾匹黑馬,他們說不定可以直接跳到終點!(如3 50 3 0 2 6)

那麼換種思路?我最終是從第一個元素出發,它可以到達某些臺階,我到那些臺階都是跳一次,那麼我想最快到達終點,就取決於他們那些臺階哪個可以更快到達終點了,以此類推,很容易就寫出遞歸函數了,但是!!真的可以這麼簡單嗎?如果給你INT_MAX個很大很大的數,那程序要找到什麼時候去???
首先這個算法思路沒有問題,so,我們來做點優化看會不會好一點,==當我們這個位置加上位置上的值大於等於末尾的下標,意味着我們可以一步跳到那裏,就不用去找這其中的一個最小值了。==那麼,如果一步到不了的怎麼辦呢?難道還是要按之前說的一個個找嗎?別急,我們稍後來分析分析算法實際上做了什麼,爲了比較符合大家的思維,我們先看看非遞歸版本怎麼做。
我們遞歸是算第一個,然後變成算它能控制的那個範圍內的最小值,爲了得到最小值,我們又分別去計算那些裏的一個值是多少。最終變成了,最後一個元素到最後一個元素只需要0步,值爲0,前一個元素若不爲0,則爲1。
同理。我們非遞歸就是反過來,還原它的這個過程,從後面往前算倒推回去,分別計算後面的每一個元素到達最後一個元素的最少步數,並用一個flag數組分別記錄對應位置的最少步數就可以了。比如我們通過第k個元素的值,從它控制的那些數裏找到記錄裏的最少步數,然後設置自己的最小步數爲那個的最少步數加1,自己到那個元素,然後從它跑到終點去;然後再計算通過k-1個元素的值去找它控制的元素記錄裏的最小值…直到找完了,那麼flag[0]就是我們要求的。
我們在這個過程中無非就是把下標不停的往左移,然後找最小值了,那能不能在這個找最小值做個優化呢?我們簡單分析分析兩個相鄰元素之間的關係。

A和B他們的值分別是l1和l2,第一次,我們遍歷了B+1到B+l2找到了最小值M,然後我們算A的,從A+1,到A+l1找到了最小值N,這個M與N有沒有聯繫呢?

我們在紙上簡單畫個數軸就會發現,其實他們很有可能是一個包含的關係的。1、當A+l1正好等於B+l2的時候,那我們其實沒必要循環算一次,因爲他們代表的區間基本一致,只多了一個B(顯然爲M+1大於M),故此時最小值就是M。2、當A+l1大於B+l2的時候,A需要遍歷的區間是比B要多的,但是B的那段區間我們已經找到了最小值,就無需再遍歷找一遍了,所以我們只要看多出的區間裏是否有比這個M更小的即可。3、當A+l1小於B+l2的時候,我暫時沒有發現什麼很好的結論,那就直接遍歷找一遍唄,找到最小的即可。最終通過這樣的分析,我們可以對這個找最小值做了大量剪枝。
好啦 Show Code

#include<cstdlib>
#include<algorithm>
class Solution {
public:
    int jump(vector<int>& nums) {
        //用數組記錄從i位置到結束最少需要幾步.若無法到則直接設置爲length+1步
        //若數組爲空,或者數組只有一個元素,則直接返回0,若只有兩個元素,則返回
        int *flag;
        int len = nums.size();
        if(len == 0 || len == 1)
            return 0;
        if(len == 2)
            return 1;
        flag = (int *)malloc(sizeof(int)*len);
        flag[len-1] = 0;
        for(int i = len-2;i >= 0;--i)
        {
            //計算第i個的時候,看它能夠到達哪些位置,並找到一個最小的,自己就爲那個+1步
            if(nums[i] == 0)
            {
                flag[i] = len+1;
                continue;
            }
            //剪枝減少計算
            //計算i位置的最小值時,先判斷自己的區間是否包括了i+1計算的區間,
            //如果包括了,且沒有多餘,那就等於min+1,如果包括了,且有多餘,則判斷多餘的有效空間裏是否存在更小的。
            //若沒有包括,則一個個找
            if(i+nums[i] >= len-1)
            {
                flag[i] = 1;
                continue;
            }
            if(i+nums[i] == i+1+nums[i+1])
                flag[i] = flag[i+1];
            else if(i+nums[i] > i+1+nums[i+1])//比它有多
            {
                int m = flag[i+1]-1;//這是之前最快到達的,現在看有沒有比它更快的
                int k = min(i+nums[i],len-2);
                    for(int j = i+2+nums[i+1];j <= k;++j)
                    if(flag[j] < m)
                        m = flag[j];
                flag[i] = m+1;
            }
            else
            {
                //比那個範圍要小,那就只能死找了
                int m = min(i+nums[i],len-1);
                for(int j = m-1;j > i;--j)
                    if(flag[j] < flag[m])
                        m = j;
                flag[i] = flag[m]+1;
            }
        }
        len = flag[0];
        free(flag);
        return len;
    }
};

不做後面的剪枝在這組數據會超時:
[25000,24999,24998,24997,24996,24995,24994,24993,…,3,2,1,1,0]

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