题目来源:https://leetcode.cn/problems/cherry-pickup/
大致题意:
给定一个二维数组1,其中数组元素由 0,1,-1 表示,有以下含义:
- 0 表示该位置可以通行
- 1 表示该位置有 1 个樱桃,经过可以摘 1 个樱桃,摘过后变为 0
- -1 表示该位置不能通行
求从左上角移动到右下角(只能往右往下移动),再从右下角移动到左上角(只能往左往上移动)最多能摘多少樱桃
思路
从右下角移动到左上角的过程等价于从左上角移动到右下角,所以本题等价于两个人从左上角移动到右下角,最多能摘多少樱桃
使用 dp[k][i1][i2] 表示两个人都走了 k 步,当前第一个人在 i1 行、第二个人在 i2 行的分数(摘樱桃个数)
因为方向已知,所以可以通过步数和行求出列,比如走了 k 步,当前第一个人在 i1 行时,第一个人在 k - i1 列
那么 dp[k][i1][i2] 可以从 dp[k - 1][i1][i2]、dp[k - 1][i1 - 1][i2]、dp[k - 1][i1][i2 - 1]、dp[k - 1][i1 - 1][i2 - 1] 四个组合到达,取四个组合的最大得分加上当前两个人所在位置的樱桃数即可
需要注意的是,如果当前两个人位置相同且当前位置都有樱桃,那么当前两个人所在位置的樱桃数为 1 而不是 2(1 个樱桃只能摘一次)
代码:
public int cherryPickup(int[][] grid) {
int n = grid.length;
int m = n * 2 - 1;
// dp[k][i1][i2] 两个人都走了 k 步,当前第一个人在 i1 行、第二个人在 i2 行的分数(摘樱桃个数)
int[][][] dp = new int[m][n][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
Arrays.fill(dp[i][j], Integer.MIN_VALUE);
}
}
// 出发时的状态
dp[0][0][0] = grid[0][0];
for (int k = 1; k < m; k++) {
// 枚举第一个人的位置
// Math.max(k - n + 1, 0) 是当前第 k 步时,行能取的最小值
// Math.min(k, n - 1) 是当前第 k 步时,行能取的最大值
for (int i1 = Math.max(k - n + 1, 0); i1 <= Math.min(k, n - 1); i1++) {
// 求出第一个人的列
int j1 = k - i1;
// 如果该位置不能通行,直接跳过
if (grid[i1][j1] == -1) {
continue;
}
// 枚举第二个人的位置
for (int i2 = Math.max(k - n + 1, 0); i2 <= Math.min(k, n - 1); i2++) {
// 求出第二个人的列
int j2 = k - i2;
// 如果该位置不能通行,直接跳过
if (grid[i2][j2] == -1) {
continue;
}
// 求出能到达当前位置的最大得分
int res = dp[k - 1][i1][i2];
if (i1 > 0) {
res = Math.max(res, dp[k - 1][i1 - 1][i2]);
}
if (i2 > 0) {
res = Math.max(res, dp[k - 1][i1][i2 - 1]);
}
if (i1 > 0 && i2 > 0) {
res = Math.max(res, dp[k - 1][i1 - 1][i2 - 1]);
}
// 加上当前位置樱桃数
res += grid[i1][j1];
// 若位置相同,樱桃数只加一次
if (i1 != i2) {
res += grid[i2][j2];
}
dp[k][i1][i2] = res;
}
}
}
// 有可能不能到达右下角
return Math.max(dp[m - 1][n - 1][n - 1], 0);
}