1. 程式人生 > >leetcode [Greedy] No.45 Jump Game II

leetcode [Greedy] No.45 Jump Game II

題目描述

Given an array of non-negative integers, you are initially positioned at the first index of the array.Each element in the array represents your maximum jump length at that position.Your goal is to reach the last index in the minimum number of jumps.

Example:

Input: [2,3,1,1,4] Output: 2 Explanation:

The minimum number of jumps to reach the last index is 2.Jump 1 step from index 0 to 1, then 3 steps to the last index.

Note: You can assume that you can always reach the last index.

解題思路

思路一

看到這道題,一開始我想到的是一種類似動態規劃的解法,即維護一個數組,該陣列記錄到當前位置所需要的最小步數。總的演算法步驟是一個二層迴圈,我們首先遍歷輸入所給的陣列,在訪問每個元素的時候,從當前元素的位置,迴圈到從當前位置可以跳到的最遠距離,每次比較當前位置的步數+1是否比之後的元素步數要小,如果小,則更新之後元素的最小步數。當我們遍歷完整個陣列的時候,答案即是該陣列的最後一個元素的值。

程式碼:

class Solution {
public:
    int jump(vector<int>& nums) {
        int size = nums.size();
        vector<int> vec(size, -1);
        vec[0] = 0;
        for (int i = 0; i < size; i++) {
            for (int j = i+1; j < min(size, nums[i] + i + 1); j++) {
                if (
vec[j] == -1 || vec[j] > vec[i] + 1) { vec[j] = vec[i] + 1; } } } return vec[size - 1]; } };

執行結果: 在這裡插入圖片描述 然而這種演算法執行超時了,可以看出,上述演算法的效率為O(n2)O(n^2),而結果顯示,一共92組測試樣例,我們一共通過了91組,最後一組樣例的陣列大小為25000,所以O(n2)O(n^2)的效率隊這種大小的陣列來說還是太低了。

思路二

既然O(n2)O(n^2)的效率不夠,那我們就需要想一種更加高效的演算法。我們想想,是否有一種演算法只遍歷一遍陣列就將答案求解出來呢?答案是肯定的,我們考慮下面這一種情況,當我們搜尋到某個元素aia_i時,從aia_i最遠可以跳到ai+ma_{i+m},那我們來分析下一步應該如何走。當前陣列片段為[&ThinSpace;,ai,ai+1,ai+2,&ThinSpace;,ai+m,&ThinSpace;][\cdots, a_i, a_{i+1}, a_{i+2}, \cdots, a_{i+m}, \cdots],假設aia_i的前一個元素可以跳到aja_j,則iji+mi \leq j \leq i+m,這很容易得出,因為若j&lt;ij &lt; i,則我們不可能跳到aia_i,若j&gt;i+mj &gt; i+m,那麼我們上一步為什麼不直接跳到aja_j呢(假設上一步的決策是正確的)?那麼我們可以得到,在做出上一步的決策的時候,我們已經訪問過了[ai,aj][a_i,a_j]這個區間,且得出跳到aia_i是最好的決策,所以在區間[ai,aj][a_i, a_j]這個區間我們在做當前這步決策的時候我們就可以不用再訪問了,因為這一步的決策一定不再這個區間中國,直觀的證明: 如果我們這步決策是跳到區間[ai,aj][a_i, a_j]中,那一定有一種更優的決策,即上一步直接跳到這個區間中,在上一步的決策是正確的情況下,那我們這一步則一定不會跳到這個區間中,所以這一步我們只需訪問[aj+1,ai+m][a_{j+1},a_{i+m}]這個區間中的元素。這樣我們便保證了在求解這個問題的時候,對任意一個區間,不會重複搜尋兩次,那麼整個陣列只遍歷了一次。

我們再來討論遍歷時如何做出決策。按照貪心的思想,肯定是能跳的越遠越好,但是我們不能單純的按照當前的最遠距離跳,我們需要將眼光放的長遠一點,我們可以叫它“兩步貪心”,即我們在當前可跳的區間內,找一個元素,使得它下一步可以跳的最遠,即找到“位置+可跳距離”最大的元素,然後下一步我們就跳到這個元素。

程式碼:

class Solution {
public:
    int jump(vector<int>& nums) {
        int size = nums.size();
        int count = 0, ptr = 0, subptr = 0;
        while (ptr < size) {
            int step = 0, end = subptr + nums[subptr] + 1;
            for (; ptr < min(size, end); ptr++) {
                if (ptr + nums[ptr] >= step) {
                    step = ptr + nums[ptr];
                    subptr = ptr;
                }
            }
            count++;
        }
        return min(count, size - 1);
    }
    
};

Tips: 在評測程式碼的時候我發現,有一組樣例資料是[0][0],但是我們這個演算法中,最少會執行一步,即輸出最少為1,但是這組資料的結果應該是0,所以在輸出結果的時候我加了一個限制,即最差的結果就是陣列長度-1,即每次我們只跳一步,所以不可能會有比這更差的結果,如果我們算出來的結果比這個大,則捨棄我們計算的結果,輸出陣列長度-1.

執行結果: 在這裡插入圖片描述 因為整個陣列只訪問了一遍,所以我們很容易的可以得出,這個演算法的效率為O(n)O(n),整個執行時間只有12ms,還是很高效的。