【leetcode】741 摘櫻桃(動態規劃)
題目連結:https://leetcode-cn.com/problems/cherry-pickup/
題目描述
一個N x N的網格(grid) 代表了一塊櫻桃地,每個格子由以下三種數字的一種來表示:
-
0 表示這個格子是空的,所以你可以穿過它。
-
1 表示這個格子裡裝著一個櫻桃,你可以摘到櫻桃然後穿過它。
-
-1 表示這個格子裡有荊棘,擋著你的路。
你的任務是在遵守下列規則的情況下,儘可能的摘到最多櫻桃:
- 從位置 (0, 0) 出發,最後到達 (N-1, N-1) ,只能向下或向右走,並且只能穿越有效的格子(即只可以穿過值為0或者1的格子);
- 當到達 (N-1, N-1) 後,你要繼續走,直到返回到 (0, 0) ,只能向上或向左走,並且只能穿越有效的格子;
- 當你經過一個格子且這個格子包含一個櫻桃時,你將摘到櫻桃並且這個格子會變成空的(值變為0);
- 如果在 (0, 0) 和 (N-1, N-1) 之間不存在一條可經過的路徑,則沒有任何一個櫻桃能被摘到。
示例 1:
輸入: grid =
[[0, 1, -1],
[1, 0, -1],
[1, 1, 1]]
輸出: 5
解釋:
玩家從(0,0)點出發,經過了向下走,向下走,向右走,向右走,到達了點(2, 2)。
在這趟單程中,總共摘到了4顆櫻桃,矩陣變成了[[0,1,-1],[0,0,-1],[0,0,0]]。
接著,這名玩家向左走,向上走,向上走,向左走,返回了起始點,又摘到了1顆櫻桃。
在旅程中,總共摘到了5顆櫻桃,這是可以摘到的最大值了。
說明:
- grid 是一個 N * N 的二維陣列,N的取值範圍是1 <= N <= 50。
- 每一個
grid[i][j]
都是集合 {-1, 0, 1}其中的一個數。 - 可以保證起點
grid[0][0]
和終點grid[N-1][N-1]
的值都不會是 -1。
思路
題目的詳解見英文leetcode原帖:https://leetcode.com/problems/cherry-pickup/discuss/109903/Step-by-step-guidance-of-the-O(N3)-time-and-O(N2)-space-solution
因為題目限制了grid[i][j]=1
的節點只能訪問一次,所以核心的問題在於要避免partI和partII重複計數。
I 暴力嘗試
暴力回溯,指數級時間複雜度。每趟來回 (4N-4)
步,可能的往返數 2^(4N-4)
。
II 動態規劃嘗試
(0, 0) ==> (N-1, N-1)
和 (N-1, N-1)==>(0,0)
分別使用動態規劃得到其子問題最優解,但是總體 (0, 0) ==> (N-1, N-1) ==> (0, 0)
不一定是最優解。
grid = [[1,1,1,0,1],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[1,0,1,1,1]].
如上式在partI可以得到最優解,(0, 0) ==> (0, 2) ==> (4, 2) ==> (4, 4)
總和為6,對應的partII最多總和為1,總體來回總和為7。然後沿著矩形邊緣可以得到所有的8個櫻桃。所以這個貪心策略不一定能得到最優解。
III 修改grid矩陣的動態規劃嘗試
記錄當前grid的狀態,空間複雜度太高了
IV 最終版-不修改grid矩陣的動態規劃
是否可以縮短路程,不需要走到右下角?YES!
我們重新定義 T(i,j)
為簡化路程 (0, 0) ==> (i, j) ==> (0, 0)
可以得到的最大數,無需修改輸入矩陣。此時原問題可以表示為T(N-1,N-1)
。為了得到遞推關係式:
對於每個座標 (i,j)
,我們有兩種方式到達以及兩種方式離開該點:(i-1, j)
和(i, j-1)
,往返路程可以分為以下四個case:
Case 1: (0, 0) ==> (i-1, j) ==> (i, j) ==> (i-1, j) ==> (0, 0)
Case 2: (0, 0) ==> (i, j-1) ==> (i, j) ==> (i, j-1) ==> (0, 0)
Case 3: (0, 0) ==> (i-1, j) ==> (i, j) ==> (i, j-1) ==> (0, 0)
Case 4: (0, 0) ==> (i, j-1) ==> (i, j) ==> (i-1, j) ==> (0, 0)
根據定義,case1等價於T(i-1, j) + grid[i][j]
,case2等價於T(i, j-1) + grid[i][j]
。但是我們定義的T(i, j)
並沒有覆蓋最後兩種情形:PartI最後一步和PartII第一步不同。因此我們需要修改T(i,j)
定義,擴充套件為T(i, j, p, q)
,表明兩段路程(0, 0) ==> (i, j); (p, q) ==> (0, 0)
的最大櫻桃數,無需修改gird
矩陣。
類似上文所述,我們有兩種方式到達座標(i,j)
,兩種方式離開座標(p,q)
Case 1: (0, 0) ==> (i-1, j) ==> (i, j); (p, q) ==> (p-1, q) ==> (0, 0)
Case 2: (0, 0) ==> (i-1, j) ==> (i, j); (p, q) ==> (p, q-1) ==> (0, 0)
Case 3: (0, 0) ==> (i, j-1) ==> (i, j); (p, q) ==> (p-1, q) ==> (0, 0)
Case 4: (0, 0) ==> (i, j-1) ==> (i, j); (p, q) ==> (p, q-1) ==> (0, 0)
根據定義得到:
Case 1 is equivalent to T(i-1, j, p-1, q) + grid[i][j] + grid[p][q]
;
Case 2 is equivalent to T(i-1, j, p, q-1) + grid[i][j] + grid[p][q]
;
Case 3 is equivalent to T(i, j-1, p-1, q) + grid[i][j] + grid[p][q]
;
Case 4 is equivalent to T(i, j-1, p, q-1) + grid[i][j] + grid[p][q]
;
遞推關係式為:
T(i, j, p, q) = grid[i][j] + grid[p][q] + max{T(i-1, j, p-1, q), T(i-1, j, p, q-1), T(i, j-1, p-1, q), T(i, j-1, p, q-1)}
約束條件-避免重複計數
至此,我們需要設定約束來避免對同一格子重複計數。因為上文我們已經在計算T(i, j, p, q)
時計數了gird[i][j]
和gird[p][q]
,為了避免重複計數,這兩個gird節點不應該在 T(i-1, j, p-1, q)
, T(i-1, j, p, q-1)
, T(i, j-1, p-1, q)
and T(i, j-1, p, q-1)
這四個中任何一個的計算中被計數。
顯然 (i, j)
不可能出現在 (0, 0) ==> (i-1, j)
或 (0, 0) ==> (i, j-1)
,同理 (p, q)
不會出現在 (p-1, q) ==> (0, 0)
或 (p, q-1) ==> (0, 0)
。
因此如果我們可以保證(i, j)
不出現在(p-1, q) ==> (0, 0)
or (p, q-1) ==> (0, 0)
並且 (p, q)
不出現在 (0, 0) ==> (i-1, j)
or (0, 0) ==> (i, j-1)
,則將不會發生重複計數。怎麼做呢?
以(0, 0) ==> (i-1, j)
and (0, 0) ==> (i, j-1)
舉例。我們知道這些路徑的邊界,前者所有路徑落在矩形 [0, 0, i-1, j]
,後者所有路徑落在矩形[0, 0, i, j-1]
,這表明兩個路徑合起來將會落在矩形[0, 0, i, j]
除右下角(i,j)
的區域。因此如果我們保證 (p, q)
在矩形[0, 0, i, j]
之外(除了特殊情況在(i,j)
處重疊),它將絕不會出現在(0, 0) ==> (i-1, j)
or(0, 0) ==> (i, j-1)
的路徑上。
同理 (i, j)
也必須落在矩形 [0, 0, p, q]
之外,避免重複計數。總結得到以下三個條件之一應該為true:
i < p && j > q
i == p && j == q
i > p && j < q
這說明往返路程T(i, j, p, q)
並非對所有四個座標的取值都有效,而應該滿足上述條件。
但是 T(i, j, p, q)
不滿足self-consistency, For example, T(3, 1, 2, 3)
is valid under these conditions but one of the terms in the recurrence relations, T(2, 1, 2, 2)
, would be invalid, and we have no idea how to get its value under current definition of T(i, j, p, q)
.
Self-consistent two-leg DP definition
因此上面四個引數是互相關聯的,不是獨立引數。上述四個引數的表示式不是最簡的,我們希望能夠找到滿足上述三個條件的子集,以確保一定滿足上述條件。我們可以觀察到**:當 i(p)
增加時,我們必須讓 j(q) 減小使得上式滿足,反之亦然**(它們是負關聯的)。於是我們可以設定i
(p
) 和j
(q
) 的和為某個常數,n = i + j = p + q
。(Note in this subset of conditions, n
can be interpreted as the number of steps from the source position (0, 0)
. I have also tried other anti-correlated functions for i
and j
such as their product is a constant but it did not work out. The recurrence relations here play a role and constant sum turns out to be the simplest one that works.)
由此新條件,我們可以重寫滿足 n = i + j = p + q
的式子T(i, j, p, q)
為:
T(n, i, p)
, where T(n, i, p) = T(i, n-i, p, n-p)
.
T(i-1, n-i, p-1, n-p) = T(n-1, i-1, p-1)
T(i-1, n-i, p, n-p-1) = T(n-1, i-1, p)
T(i, n-i-1, p-1, n-p) = T(n-1, i, p-1)
T(i, n-i-1, p, n-p-1) = T(n-1, i, p)
於是得到遞迴表示式為:
T(n, i, p) = grid[i][n-i] + grid[p][n-p] + max{T(n-1, i-1, p-1), T(n-1, i-1, p), T(n-1, i, p-1), T(n-1, i, p)}.
Of course, in the recurrence relation above, only one of grid[i][n-i]
and grid[p][n-p]
will be taken if i == p
(i.e., when the two positions overlap). Also note that all four indices, i
, j
, p
and q
, are in the range [0, N)
, meaning n
will be in the range [0, 2N-1)
(remember it is the sum of i
and j
). Lastly we have the base case given by T(0, 0, 0) = grid[0][0]
.
上述式子,時間複雜度:O(n3),空間複雜度:O(n3);但是觀察到T(n, i, p)
only depends on those subproblems with n - 1
,所以空間複雜度可以降為O(n^2)。
程式碼
/*
* 動態規劃
* 時間複雜度O(n^3) 空間複雜度O(n^2)
*/
class Solution {
public:
int cherryPickup(vector<vector<int>>& grid) {
int N = grid.size(), M = (N<< 1) -1; // M表示從(0,0)到(N-1,N-1)的格子數
vector<vector<int>> dp(N, vector<int> (N,0));
dp[0][0] = grid[0][0];
for (int n = 1; n < M; ++n) {
for (int i = N-1; i >= 0; --i) {
for (int p = N-1; p >=0 ; --p) {
int j = n - i, q = n-p;
// 出界判斷,出現障礙
if (j < 0 || j >= N || q < 0 || q >= N || grid[i][j] < 0 || grid[p][q] < 0) {
dp[i][p] = -1;
continue;
}
if(i>0) dp[i][p] = max(dp[i][p], dp[i-1][p]);
if (p > 0) dp[i][p] = max(dp[i][p], dp[i][p - 1]);
if (i > 0 && p > 0) dp[i][p] = max(dp[i][p], dp[i - 1][p - 1]);
if (dp[i][p] >= 0) dp[i][p] += grid[i][j] + (i != p ? grid[p][q] : 0);
}
}
}
return max(dp[N-1][N-1],0);
}
};
相關文章
- [leetcode] 動態規劃(Ⅰ)LeetCode動態規劃
- leetcode:動態規劃( hard )LeetCode動態規劃
- leetcode總結——動態規劃LeetCode動態規劃
- leetcode題解(動態規劃)LeetCode動態規劃
- LeetCode動態規劃總結LeetCode動態規劃
- 【LeetCode】Word Break 動態規劃LeetCode動態規劃
- leetcode-動態規劃總結LeetCode動態規劃
- 【LeetCode】Word Break II 動態規劃LeetCode動態規劃
- 【動態規劃(一)】動態規劃基礎動態規劃
- [LeetCode] 動態規劃題型總結LeetCode動態規劃
- Leetcode 編輯距離(動態規劃)LeetCode動態規劃
- LeetCode 動態規劃 House Robber 習題LeetCode動態規劃
- [leetcode 1235] [動態規劃]LeetCode動態規劃
- 動態規劃動態規劃
- LeetCode 分割回文串II(動態規劃)LeetCode動態規劃
- LeetCode入門指南 之 動態規劃思想LeetCode動態規劃
- LeetCode:動態規劃+貪心題目整理LeetCode動態規劃
- LeetCode 343. 整數拆分--動態規劃LeetCode動態規劃
- 【LeetCode】55. 跳躍遊戲 (動態規劃)LeetCode遊戲動態規劃
- 動態規劃分析動態規劃
- 動態規劃(DP)動態規劃
- 動態規劃初步動態規劃
- 模板 - 動態規劃動態規劃
- 動態規劃法動態規劃
- Leetcode 題解演算法之動態規劃LeetCode演算法動態規劃
- LeetCode總結,動態規劃問題小結LeetCode動態規劃
- 演算法系列-動態規劃(1):初識動態規劃演算法動態規劃
- [leetcode初級演算法]動態規劃總結LeetCode演算法動態規劃
- 【LeetCode】Wildcard Matching 串匹配 動態規劃LeetCode動態規劃
- 淺談動態規劃動態規劃
- 有關動態規劃動態規劃
- 動態規劃小結動態規劃
- 動態規劃初級動態規劃
- 動態規劃講義動態規劃
- 好題——動態規劃動態規劃
- 3.動態規劃動態規劃
- 動態規劃-----線性動態規劃
- 動態規劃專題動態規劃