[LeetCode] Cherry Pickup 撿櫻桃


 

In a N x N grid representing a field of cherries, each cell is one of three possible integers.

 

  • 0 means the cell is empty, so you can pass through;
  • 1 means the cell contains a cherry, that you can pick up and pass through;
  • -1 means the cell contains a thorn that blocks your way.

 

Your task is to collect maximum number of cherries possible by following the rules below:

 

  • Starting at the position (0, 0) and reaching (N-1, N-1) by moving right or down through valid path cells (cells with value 0 or 1);
  • After reaching (N-1, N-1), returning to (0, 0) by moving left or up through valid path cells;
  • When passing through a path cell containing a cherry, you pick it up and the cell becomes an empty cell (0);
  • If there is no valid path between (0, 0) and (N-1, N-1), then no cherries can be collected.

 

 

Example 1:

Input: grid =
[[0, 1, -1],
 [1, 0, -1],
 [1, 1,  1]]
Output: 5
Explanation: 
The player started at (0, 0) and went down, down, right right to reach (2, 2).
4 cherries were picked up during this single trip, and the matrix becomes [[0,1,-1],[0,0,-1],[0,0,0]].
Then, the player went left, up, up, left to return home, picking up one more cherry.
The total number of cherries picked up is 5, and this is the maximum possible.

 

Note:

  • grid is an N by N 2D array, with 1 <= N <= 50.
  • Each grid[i][j] is an integer in the set {-1, 0, 1}.
  • It is guaranteed that grid[0][0] and grid[N-1][N-1] are not -1.

 

這道題給了我們一個二維數組,每個數字只有三個數字,-1,0,和1,其中-1表示障礙物不能通過,1表示有櫻桃並可以通過,0表示沒有櫻桃並可以通過,並設定左上角為起點,右下角為終點,讓我們從起點走到終點,再從終點返回起點,求最多能撿的櫻桃的個數,限定起點和終點都沒有障礙物。博主開始想的是就用dp來做唄,先從起點走到終點,求最多能撿多個櫻桃,然后將撿起櫻桃后將grid值變為0,然后再走一遍,把兩次得到的櫻桃數相加即可,但是類似貪婪算法的dp解法卻跪在了下面這個case:

 

1 1 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 1
1 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 1 1 1

 

我們可以看出,紅色的軌跡是第一次dp解法走過的路徑,共拿到了13個櫻桃,但是回到起點的話,剩下的兩個櫻桃無論如何也不可能同時拿到,只能拿到1顆,所以總共只能撿到14顆櫻桃,而實際上所有的櫻桃都可以撿到,需要換個走法的話,比如下面這種走法:

 

1 1 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 1
1 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 1 1 1

 

紅色為從起點到終點的走法,共拿到9顆櫻桃,回去走藍色的路徑,可拿到6顆櫻桃,所以總共15顆都能收入囊中。那這是怎么回事,原因出在了我們的dp遞推式的設計上,博主之前設計式,當前位置的櫻桃數跟上邊和左邊的櫻桃數有關,取二者的較大值,如果只是從起點到終點走單程的話,這種設計是沒有問題的,可以拿到最多的櫻桃,但如果是round trip的話,那么就不行了。這里參考的還是fun4LeetCode大神的帖子,范佛利特扣德大神的帖子每次講解都寫的巨詳細,總是讓博主有種讀paper的感覺。博主就挑選部分來講講,完整版可以自己去讀一讀大神的親筆~

最開始時博主定義的dp[i][j]為單程的,即到達(i, j)位置能撿到的最大櫻桃數,即:

T(i, j) = grid[i][j] + max{ T(i-1, j), T(i, j-1) }

但是定義單程就得改變grid的值,再進行一次dp計算時,就會陷入之前例子中的陷阱。所以我們的dp[i][j]還是需要定義為round trip的,即到達(i, j)位置並返回起點時能撿到的最大櫻桃數,但是新的問題就來了,櫻桃只有一個,只能撿一次,去程撿了,返程就不能再撿了,如何才能避免重復計算呢?我們只有i和j是不夠的,其只能定義去程的位置,我們還需要pg,(不是pgone哈哈),來定義返程的位置,那么重現關系Recurrence Relations就變成了 T(i, j, p, g),我們有分別兩種方式離開(i, j)和(p, g),我們suppose時從終點往起點遍歷,那么就有4種情況:

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)}

為了避免重復計算,我們希望 grid[i][j] 和 grid[p][g] 不出現在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)中的任意一個上。顯而易見的是(i, j)不會出現在(0, 0) ==> (i-1, j) 或 (0, 0) ==> (i, j-1) 的路徑上,同理,(p, g) 也不會出現在 (p-1, q) ==> (0, 0) 或 (p, q-1) ==> (0, 0) 的路徑上。因此,我們需要保證(i, j) 不會出現在 (p-1, q) ==> (0, 0) 或 (p, q-1) ==> (0, 0) 的路徑上,同時 (p, g)不會出現在(0, 0) ==> (i-1, j) 或 (0, 0) ==> (i, j-1) 的路徑上,怎么做呢?

我們觀察到(0, 0) ==> (i-1, j) 和 (0, 0) ==> (i, j-1) 的所有點都在矩形 [0, 0, i, j] 中(除了右下角點(i, j)點),所以只要 (p, g) 不在矩形 [0, 0, i, j] 中就行了,注意(p, g) 和 (i, j) 是有可能重合了,這種情況特殊處理一下就行了。同理, (i, j) 也不能在矩形 [0, 0, p, g] 中,那么以下三個條件中需要滿足一個:

i < p && j > q
i == p && j == q
i > p && j < q

為了滿足上述條件,我們希望當 i 或 p 增加的時候,j 或 q 減小,那么我們可以有這個等式:

k = i + j = p + q

其中k為從起點開始走的步數,所以我們可以用 T(k, i, p)  來代替 T(i, j, p, g),那么我們的重現關系式就變成了:

T(k, i, p) = grid[i][k-i] + grid[p][k-p] + max{T(k-1, i-1, p-1), T(k-1, i-1, p), T(k-1, i, p-1), T(k-1, i, p)}.

當 i == p 時,grid[i][k-i] 和 grid[p][k-p] 就相等了,此時只能加一個。我們注意到 i, j, p, q 的范圍是 [0, n), 意味着k只能在范圍 [0, 2n - 1) 中, 初始化時 T(0, 0, 0) = grid[0][0]。我們這里的重現關系T雖然是三維的,但是我們可以用二維dp數組來實現,因為第k步的值只依賴於第k-1步的情況,參見代碼如下:

 

class Solution {
public:
    int cherryPickup(vector<vector<int>>& grid) {
        int n = grid.size(), mx = 2 * n - 1;
        vector<vector<int>> dp(n, vector<int>(n, -1));
        dp[0][0] = grid[0][0];
        for (int k = 1; k < mx; ++k) {
            for (int i = n - 1; i >= 0; --i) {
                for (int p = n - 1; p >= 0; --p) {
                    int j = k - i, q = k - 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);
    }
};

 

類似題目:

Minimum Path Sum

Dungeon Game

 

參考資料:

https://discuss.leetcode.com/topic/112877/annotated-c-dp-solution

https://discuss.leetcode.com/topic/113762/step-by-step-guidance-of-the-o-n-3-time-and-o-n-2-space-solution

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM