最大路徑和問題(摘櫻桃問題)
作者:Grey
原文地址: 最大路徑和問題(摘櫻桃問題)
題目鏈接
主要思路
本題的難點在於嘗試,如何模擬一來一回的情況,我們可以這樣做,定義兩個小人,兩個人都從(0,0)
位置出發,到右下角位置,每人同時選擇不同的下一步,如果兩個小人跳到了同一個位置,只計算一份。由於兩個小人是同時走,而且每次只走一步,所以,兩個小人一定是同時到達右下角的,兩個小人一路收集的櫻桃數量,就是一來一回收集的數量。
我們可以寫出第一個嘗試版本,定義遞歸函數
int p(int[][] matrix, int m, int n, int x1, int y1, int x2, int y2)
遞歸函數的含義表示,第一個小人從(x1,y1)
開始到右下角,第二個小人從(x2,y2)
開始到右下角,獲取到的最大值是多少。所以p(matrix, m, n, 0, 0, 0, 0)
就是我們需要的答案。
接下來考慮base case
,即:兩個小人都到達了右下角,由於題目已經說了,右下角的值一定不是-1,所以,可以獲得一份櫻桃數量。
if (x1 == m - 1 && y1 == n - 1) {
// 已經到了最右下角了
// 隱含條件:另外一個點也一定到達右下角
// 獲得一份櫻桃數
return matrix[x1][y1];
}
接下來就是普遍嘗試,分別可以分成如下四種情況:
情況1:第一個小人往下,第二個小人往右。
情況2:第一個小人往下,第二個小人往下。
情況3:第一個小人往右,第二個小人往下。
情況4:第一個小人往右,第二個小人往右。
但是在走上述任何分支的時候,記得要滿足兩個條件
第一個條件,不能越界
第二個條件,下一個要走的位置不能是-1,因爲題目說到,-1是不能走的位置。
int next = -1;
// 下,下
if (x1 + 1 < m && x2 + 1 < m && matrix[x1 + 1][y1] != -1 && matrix[x2 + 1][y2] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2 + 1, y2), next);
}
// 下,右
if (x1 + 1 < m && y2 + 1 < n && matrix[x1 + 1][y1] != -1 && matrix[x2][y2 + 1] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2, y2 + 1), next);
}
// 右,下
if (y1 + 1 < n && x2 + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2 + 1][y2] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2 + 1, y2), next);
}
// 右,右
if (y1 + 1 < n && y2 + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2][y2 + 1] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2, y2 + 1), next);
}
經過上述四個分支,如果next
的值還是-1,說明無路可走,返回-1
if (next == -1) {
return -1;
}
如果next
不等於-1,說明有可以走的路徑,那麼繼續判斷兩個小人是否在同一位置,如果在同一個位置,則只收集一份櫻桃,如果不在同一個位置,收集兩個小人所在位置的櫻桃之和。
if (x1 == x2) {
// 到達同一個位置,只取一個值
return next + matrix[x1][y1];
}
return next + matrix[x1][y1] + matrix[x2][y2];
注:判斷同一個位置,只需要一個維度上的座標相等即可。
完整代碼
public static int cherryPickup1(int[][] matrix) {
return Math.max(0, p(matrix, matrix.length, matrix[0].length, 0, 0, 0, 0));
}
// 定義兩個小人,兩個人都從(0,0)位置出發,到右下角位置,每人同時選擇不同的下一步,如果兩個小人跳到了同一個位置,只計算一份。
public static int p(int[][] matrix, int m, int n, int x1, int y1, int x2, int y2) {
if (x1 == m - 1 && y1 == n - 1) {
// 已經到了最右下角了
// 隱含條件:另外一個點也一定到達右下角
// 獲得一份櫻桃數
return matrix[x1][y1];
}
int next = -1;
// 下,下
if (x1 + 1 < m && x2 + 1 < m && matrix[x1 + 1][y1] != -1 && matrix[x2 + 1][y2] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2 + 1, y2), next);
}
// 下,右
if (x1 + 1 < m && y2 + 1 < n && matrix[x1 + 1][y1] != -1 && matrix[x2][y2 + 1] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2, y2 + 1), next);
}
// 右,下
if (y1 + 1 < n && x2 + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2 + 1][y2] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2 + 1, y2), next);
}
// 右,右
if (y1 + 1 < n && y2 + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2][y2 + 1] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2, y2 + 1), next);
}
if (next == -1) {
return -1;
}
if (x1 == x2) {
// 到達同一個位置,只取一個值
return next + matrix[x1][y1];
}
return next + matrix[x1][y1] + matrix[x2][y2];
}
這個解法在 LeetCode 上直接超時。
在上述嘗試的基礎上,我們可以做進一步的優化,遞歸函數現在是四個可變參數,根據我們的設計,其實可以得到如下公式
x1 + y1 = x2 + y2
那麼我們可以將遞歸函數省略一個參數y2
,因爲
y2 = x1 + y1 - x2
上述遞歸方法我們可以修改爲
// 省略y2參數
public static int p(int[][] matrix, int m, int n, int x1, int y1, int x2) {
if (x1 == m - 1 && y1 == n - 1) {
// 已經到了最右下角了
// 隱含條件:另外一個點也一定到達右下角
// 獲得一份櫻桃數
return matrix[x1][y1];
}
int next = -1;
// 下,下
if (x1 + 1 < m && x2 + 1 < m && matrix[x1 + 1][y1] != -1 && matrix[x2 + 1][getY2(x1, y1, x2)] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2 + 1), next);
}
// 下,右
if (x1 + 1 < m && getY2(x1, y1, x2) + 1 < n && matrix[x1 + 1][y1] != -1 && matrix[x2][getY2(x1, y1, x2) + 1] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2), next);
}
// 右,下
if (y1 + 1 < n && x2 + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2 + 1][getY2(x1, y1, x2)] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2 + 1), next);
}
// 右,右
if (y1 + 1 < n && getY2(x1, y1, x2) + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2][getY2(x1, y1, x2) + 1] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2), next);
}
if (next == -1) {
return -1;
}
if (x1 == x2) {
// 到達同一個位置,只取一個值
return next + matrix[x1][y1];
}
return next + matrix[x1][y1] + matrix[x2][getY2(x1, y1, x2)];
}
public static int getY2(int x1, int y1, int x2) {
return x1 + y1 - x2;
}
我們可以做進一步的優化,由於三個可變參數的範圍分別是[0...m-1]
,[0...n-1]
,[0...m-1]
,那麼我們可以設置三維dp
,將所有遞歸結果緩存起來
int[][][] dp = new int[m][n][m];
三維dp
的所有位置初始值設置爲Integer.MIN_VALUE
,在遞歸方法中,將這個三維表作爲緩存帶入參數中,每次遞歸過程中,先從dp
表中拿值,如果值不爲Integer.MIN_VALUE
,說明這個過程算過,直接取值即可。
public static int p(int[][] matrix, int m, int n, int x1, int y1, int x2, int[][][] dp) {
if (dp[x1][y1][x2] != Integer.MIN_VALUE) {
// 說明這個過程曾經算過,直接從緩存中取值即可
return dp[x1][y1][x2];
}
}
然後在每次遞歸過程中,將答案存入緩存,完整代碼如下
// 動態規劃
// 三維表
public static int cherryPickup(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
int[][][] dp = new int[m][n][m];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < m; k++) {
// 初始值設置爲Integer.MIN_VALUE,只要不等於Integer.MIN_VALUE,就說明遞歸過程已經算過了
dp[i][j][k] = Integer.MIN_VALUE;
}
}
}
return Math.max(0, p(matrix, m, n, 0, 0, 0, dp));
}
// 定義兩個小人,兩個人都從(0,0)位置出發,到右下角位置,每人同時選擇不同的下一步,如果兩個小人跳到了同一個位置,只計算一份。
public static int p(int[][] matrix, int m, int n, int x1, int y1, int x2, int[][][] dp) {
if (dp[x1][y1][x2] != Integer.MIN_VALUE) {
// 說明這個過程曾經算過,直接從緩存中取值即可
return dp[x1][y1][x2];
}
if (x1 == m - 1 && y1 == n - 1) {
// 已經到了最右下角了
// 隱含條件:另外一個點也一定到達右下角
// 獲得一份櫻桃數
dp[x1][y1][x2] = matrix[x1][y1];
return matrix[x1][y1];
}
int next = -1;
// 下,下
if (x1 + 1 < m && x2 + 1 < m && matrix[x1 + 1][y1] != -1 && matrix[x2 + 1][getY2(x1, y1, x2)] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2 + 1, dp), next);
}
// 下,右
if (x1 + 1 < m && getY2(x1, y1, x2) + 1 < n && matrix[x1 + 1][y1] != -1 && matrix[x2][getY2(x1, y1, x2) + 1] != -1) {
next = Math.max(p(matrix, m, n, x1 + 1, y1, x2, dp), next);
}
// 右,下
if (y1 + 1 < n && x2 + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2 + 1][getY2(x1, y1, x2)] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2 + 1, dp), next);
}
// 右,右
if (y1 + 1 < n && getY2(x1, y1, x2) + 1 < m && matrix[x1][y1 + 1] != -1 && matrix[x2][getY2(x1, y1, x2) + 1] != -1) {
next = Math.max(p(matrix, m, n, x1, y1 + 1, x2, dp), next);
}
if (next == -1) {
dp[x1][y1][x2] = -1;
return -1;
}
if (x1 == x2) {
// 到達同一個位置,只取一個值
// 將答案存入緩存
dp[x1][y1][x2] = next + matrix[x1][y1];
return dp[x1][y1][x2];
}
// 將答案存入緩存
dp[x1][y1][x2] = next + matrix[x1][y1] + matrix[x2][getY2(x1, y1, x2)];
return dp[x1][y1][x2];
}
public static int getY2(int x1, int y1, int x2) {
return x1 + y1 - x2;
}
時間複雜度O(m*n*m)
,LeetCode 中直接AC。