Given an array A
(index starts at 1
) consisting of N integers: A1, A2, ..., AN and an integer B
. The integer B
denotes that from any place (suppose the index is i
) in the array A
, you can jump to any one of the place in the array A
indexed i+1
, i+2
, …, i+B
if this place can be jumped to. Also, if you step on the index i
, you have to pay Ai coins. If Ai is -1, it means you can’t jump to the place indexed i
in the array.
Now, you start from the place indexed 1
in the array A
, and your aim is to reach the place indexed N
using the minimum coins. You need to return the path of indexes (starting from 1 to N) in the array you should take to get to the place indexed N
using minimum coins.
If there are multiple paths with the same cost, return the lexicographically smallest such path.
If it's not possible to reach the place indexed N then you need to return an empty array.
Example 1:
Input: [1,2,4,-1,2], 2 Output: [1,3,5]
Example 2:
Input: [1,2,4,-1,2], 1 Output: []
Note:
- Path Pa1, Pa2, ..., Pan is lexicographically smaller than Pb1, Pb2, ..., Pbm, if and only if at the first
i
where Pai and Pbi differ, Pai < Pbi; when no suchi
exists, thenn
<m
. - A1 >= 0. A2, ..., AN (if exist) will in the range of [-1, 100].
- Length of A is in the range of [1, 1000].
- B is in the range of [1, 100].
這道題給了我們一個數組A,又給了我們一個整數B,表示能走的最大步數,數組上的每個數字都是cost值,如果到達某個位置,就要加上該位置上的數字,其實位置是在第一個數字上,目標是到達末尾位置,我們需要讓總cost值最小,並輸入路徑,如果cos相同的話,輸出字母順序小的那個路徑。還有就是如果數組上的某個位置為-1的話,表示到達該位置后不能再去下一個位置,而且數組末位置不能為-1。博主最開始寫了一個遞歸的解法,結果MLE了,看來這道題對內存使用的管控極為苛刻。所以我們不能將所有的候選路徑都存在內存中,而是應該建立祖先數組,即數組上每個位置放其父結點的位置,有點像聯合查找Union Find中的root數組,再最后根據這個祖先數組來找出正確的路徑。由於需要找出cost最小的路徑,所以我們可以考慮用dp數組,其中dp[i]表示從開頭到位置i的最小cost值,但是如果我們從后往前跳,那么dp[i]就是從末尾到位置i的最小cost值。
我們首先判斷數組A的末尾數字是否為-1,是的話直接返回空集。否則就新建結果res數組,dp數組,和pos數組,其中dp數組都初始化為整型最大值,pos數組都初始化為-1。然后將dp數組的最后一個數字賦值為數組A的尾元素。因為我們要從后往前跳,那我們從后往前遍歷,如果遇到數字-1,說明不能往前跳了,直接continue繼續循環,然后對於每個遍歷到的數字,我們都要遍歷其上一步可能的位置的dp[j]值來更新當前dp[i]值,由於限制了步數B,所以最多能到i+B,為了防止越界,要取i+B和n-1中的較小值為界限,如果上一步dp[j]值為INT_MAX,說明上一個位置無法跳過來,直接continue,否則看上一個位置dp[j]值加上當前cost值A[i],如果小於dp[i],說明dp[i]需要更新,並且建立祖先數組的映射pos[i] = j。最后在循環結束后,我們判斷dp[0]的值,如果是INT_MAX,說明沒有跳到首位置,直接返回空集,否則我們就通過pos數組來取路徑。我們從前往后遍歷pos數組來取位置,直到遇到-1停止。另外要說明的就是,這種從后往前遍歷的模式得到的路徑一定是字母順序最小的, zestypanda大神的帖子中有證明,不過博主沒太看懂-.-|||,可以帶這個例子嘗試:
A = [0, 0, 0], B = 2
上面這個例子得到的結果是[1, 2, 3],是字母順序最小的路徑,而相同的cost路徑[1, 3],就不是字母順序最小的路徑,參見代碼如下:
解法一:
class Solution { public: vector<int> cheapestJump(vector<int>& A, int B) { if (A.back() == -1) return {}; int n = A.size(); vector<int> res, dp(n, INT_MAX), pos(n, -1); dp[n - 1] = A[n - 1]; for (int i = n - 2; i >= 0; --i) { if (A[i] == -1) continue; for (int j = i + 1; j <= min(i + B, n - 1); ++j) { if (dp[j] == INT_MAX) continue; if (A[i] + dp[j] < dp[i]) { dp[i] = A[i] + dp[j]; pos[i] = j; } } } if (dp[0] == INT_MAX) return res; for (int cur = 0; cur != -1; cur = pos[cur]) { res.push_back(cur + 1); } return res; } };
下面這種方法是正向遍歷的解法,正向跳的話就需要另一個數組len,len[i]表示從開頭到達位置i的路徑的長度,如果兩個路徑的cost相同,那么一定是路徑長度大的字母順序小,可以參見例子 A = [0, 0, 0], B = 2。
具體的寫法就不講了,跟上面十分類似,參考上面的講解,需要注意的就是更新的判定條件中多了一個t == dp[i] && len[i] < len[j] + 1,就是判斷當cost相同時,我們取長度大路徑當作結果保存。還有就是最后查找路徑時要從末尾往前遍歷,只要遇到-1時停止,參見代碼如下:
解法二:
class Solution { public: vector<int> cheapestJump(vector<int>& A, int B) { if (A.back() == -1) return {}; int n = A.size(); vector<int> res, dp(n, INT_MAX), pos(n, -1), len(n, 0); dp[0] = 0; for (int i = 0; i < n; ++i) { if (A[i] == -1) continue; for (int j = max(0, i - B); j < i; ++j) { if (dp[j] == INT_MAX) continue; int t = A[i] + dp[j]; if (t < dp[i] || (t == dp[i] && len[i] < len[j] + 1)) { dp[i] = t; pos[i] = j; len[i] = len[j] + 1; } } } if (dp[n - 1] == INT_MAX) return res; for (int cur = n - 1; cur != -1; cur = pos[cur]) { res.insert(res.begin(), cur + 1); } return res; } };
類似題目:
參考資料:
https://discuss.leetcode.com/topic/98399/c-dp-o-nb-time-o-n-space
https://discuss.leetcode.com/topic/98491/java-22-lines-solution-with-proof