1. 程式人生 > >LeetCode第45之 Jump Game II

LeetCode第45之 Jump Game II

看了好幾種演算法實現,感覺這裡說的最清楚,也比較簡單,本演算法思想也是來源於此。
解法

看完這道題目,可能大部分的讀者都能夠想出這樣一個相對簡單的解法:將每個位置都看作一個點,並從第i個點向它之後的nums[i]個點都連一條長度為1的有向邊,而現在的問題就是從0號點到達size-1號點需要的最短距離,這就是一個很簡單的最短路問題,實際上由於邊的長度均為1,而且不存在環,我們可以用寬度優先搜尋(時間複雜度為O(n^2),即邊數)來進行相關的計算。

但是這樣一道難度為Hard的題會讓我們就這麼簡單通過麼?筆者覺得是不會的,所以這題肯定還存在著一些優化。

不難發現,這道題目轉換出的最短路問題存在三個條件:

(1)邊的長度均為1
(2)不存在環
(3)連出的邊是連續的
我們是不是可以用這三個“很強”的條件來做一些優化呢,答案自然是肯定的!

——如果令f[i]表示從0號點到達i號點的最短路徑,那麼對於任意i小於j,有f[i]<=f[j],即f是非遞減的,這個結論的證明是顯然的,在此不作過多贅述。

在有了這樣的結論之後,我們就會發現,其實對於f陣列來說,它會是一段段的存在,先是一個0,然後是一段1,然後是一段2,依此類推,那麼現在問題來了,每一段的長度是多少呢?

這個問題很好回答,如果我們令l[k]表示f陣列中值為k的一段的左邊界,r[k]表示f陣列中值為k的一段的有邊界,那麼有

l[k] = r[k - 1] + 1,這是顯然的
r[k] = max{i + nums[i] | l[k - 1] <= i <= r[k - 1]},由於f值為k的位置一定是從f值為k-1的位置走來的,所以只需要看從所有f值為k-1的位置裡最遠可以到達的地方即可。
也就是說,我們可以在對nums的一遍掃描中,依次求出所有的l[k]和r[k],而f陣列也就自然求解出來了——答案也就得到了。

這道題目雖然在LeetCode上標記為Hard,但是最後得出的解決方法也不算特別的複雜,主要是利用轉換後最短路問題的一些特殊性質得到了非常有用的結論,來加速了整個最短路徑的計算。
C++程式碼:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

class Solution {
public:
    int jump(vector<int>& nums) {
        int l = 0;
        int r = 0
; int next_r = 0; //k記錄跳轉到r需要的最少的次數 int k = 0; while (r<nums.size()-1) { for (int i=l;i<=r;++i) { next_r = max(next_r, i+nums[i]); } //更新k次跳轉能到達的左右兩邊下標的範圍 l = r+1; r = next_r; ++k; } return k; } }; int main() { Solution s; vector<int> v; v.push_back(2); v.push_back(3); v.push_back(1); v.push_back(1); v.push_back(4); cout<<s.jump(v)<<endl; return 0; }