LeetCode(45): 跳躍遊戲 II

Hard!html

題目描述:算法

給定一個非負整數數組,你最初位於數組的第一個位置。數組

數組中的每一個元素表明你在該位置能夠跳躍的最大長度。優化

你的目標是使用最少的跳躍次數到達數組的最後一個位置。this

示例:spa

輸入: [2,3,1,1,4]
輸出: 2
解釋: 跳到最後一個位置的最小跳躍數是 。
     從下標爲 0 跳到下標爲 1 的位置,跳  步,而後跳  步到達數組的最後一個位置。
213

說明:3d

假設你老是能夠到達數組的最後一個位置。code

解題思路:htm

這題是以前那道Jump Game 跳躍遊戲 的延伸,那題是問能不能到達最後一個數字,而此題只讓咱們求到達最後一個位置的最少跳躍數,貌似是默認必定能到達最後位置的? 此題的核心方法是利用貪婪算法Greedy的思想來解,想一想爲何呢? 爲了較快的跳到末尾,咱們想知道每一步能跳的範圍,這裏貪婪並非要在能跳的範圍中選跳力最遠的那個位置,由於這樣選下來不必定是最優解,這麼一說感受又有點不像貪婪算法了。咱們這裏貪的是一個能到達的最遠範圍,咱們遍歷當前跳躍能到的全部位置,而後根據該位置上的跳力來預測下一步能跳到的最遠距離,貪出一個最遠的範圍,一旦當這個範圍到達末尾時,當前所用的步數必定是最小步數。blog

咱們須要兩個變量cur和pre分別來保存當前的能到達的最遠位置和以前能到達的最遠位置,只要cur未達到最後一個位置則循環繼續,pre先賦值爲cur的值,表示上一次循環後能到達的最遠位置,若是當前位置i小於等於pre,說明仍是在上一跳能到達的範圍內,咱們根據當前位置加跳力來更新cur,更新cur的方法是比較當前的cur和i + A[i]之中的較大值,若是題目中未說明是否能到達末尾,咱們還能夠判斷此時pre和cur是否相等,若是相等說明cur沒有更新,即沒法到達末尾位置,返回-1。

C++解法一:

 1 class Solution {  2 public:  3     int jump(vector<int>& nums) {  4         int res = 0, n = nums.size(), i = 0, cur = 0;  5         while (cur < n - 1) {  6             ++res;  7             int pre = cur;  8             for (; i <= pre; ++i) {  9                 cur = max(cur, i + nums[i]); 10  } 11             if (pre == cur) return -1; // May not need this
12  } 13         return res; 14  } 15 };

還有一種寫法,跟上面那解法略有不一樣,可是本質的思想仍是同樣的,關於此解法的詳細分析可參見http://www.cnblogs.com/lichen782/p/leetcode_Jump_Game_II.html。這裏cur是當前能到達的最遠位置,last是上一步能到達的最遠位置,咱們遍歷數組,首先用i + nums[i]更新cur,這個在上面解法中講過了,而後判斷若是當前位置到達了last,即上一步能到達的最遠位置,說明須要再跳一次了,咱們將last賦值爲cur,而且步數res自增1,這裏咱們小優化一下,判斷若是cur到達末尾了,直接break掉便可。

C++解法二:

 1 class Solution {  2 public:  3     int jump(vector<int>& nums) {  4         int res = 0, n = nums.size(), last = 0, cur = 0;  5         for (int i = 0; i < n - 1; ++i) {  6             cur = max(cur, i + nums[i]);  7             if (i == last) {  8                 last = cur;  9                 ++res; 10                 if (cur >= n - 1) break; 11  } 12  } 13         return res; 14  } 15 };

要理解這個算法,首先明白,這個題只要咱們求跳數,怎麼跳,最後距離是多少,都沒讓求的。

大牛這個算法的思想主要是,掃描數組(廢話。。。),以肯定當前最遠能覆蓋的節點,放入curr。而後繼續掃描,直到當前的路程超過了上一次算出的覆蓋範圍,那麼更新覆蓋範圍,同時更新條數,由於咱們是通過了多一跳才能繼續前進的。

形象地說,這個是在爭取每跳最遠的greedy。舉個栗子。

好比就是咱們題目中的[2,3,1,1,4]。初始狀態是這樣的:cur表示最遠能覆蓋到的地方,用紅色表示。last表示已經覆蓋的地方,用箭頭表示。它們都指在第一個元素上。

接下來,第一元素告訴cur,最遠咱能夠走2步。因而:

下一循環中,i指向1(圖中的元素3),發現,哦,i小於last能到的範圍,因而更新last(至關於說,進入了新的勢力範圍),步數ret加1.同時要更新cur。由於最遠距離發現了。

接下來,i繼續前進,發現i在當前的勢力範圍內,無需更新last和步數ret。更新cur。

i繼續前進,接下來發現超過當前勢力範圍,更新last和步數。cur已然最大了。

最後,i到最後一個元素。依然在勢力範圍內,遍歷完成,返回ret。

這道題讓咱們明白一個道理:

不要作無必要的計算。

對了,有同窗會問,那爲啥要用last,直接用curr跳不就好了。直接用curr跳那每次都是跳最遠的,可是最優路徑不不必定是這樣。該算法時間複雜度爲O(n)。

C++解法三:

 1 /*
 2  * We use "last" to keep track of the maximum distance that has been reached  3  * by using the minimum steps "ret", whereas "curr" is the maximum distance  4  * that can be reached by using "ret+1" steps. Thus,  5  * curr = max(i+A[i]) where 0 <= i <= last.  6  */
 7 class Solution {  8 public:  9     int jump(int A[], int n) { 10         int ret = 0; 11         int last = 0; 12         int curr = 0; 13         for (int i = 0; i < n; ++i) { 14             if (i > last) { 15                 last = curr; 16                 ++ret; 17  } 18             curr = max(curr, i+A[i]); 19  } 20 
21         return ret; 22  } 23 };
相關文章
相關標籤/搜索