题目描述

这是 LeetCode 上的 ​741. 摘樱桃​ ,难度为 困难

Tag : 「线性 DP」

一个741. 摘樱桃 : 经典线性 DP 运用题_初始化 的网格( ​​​grid​​) 代表了一块樱桃地,每个格子由以下三种数字的一种来表示:

  • 741. 摘樱桃 : 经典线性 DP 运用题_算法_02
  • 741. 摘樱桃 : 经典线性 DP 运用题_初始化_03
  • 741. 摘樱桃 : 经典线性 DP 运用题_算法_04

你的任务是在遵守下列规则的情况下,尽可能的摘到最多樱桃:

  • 从位置741. 摘樱桃 : 经典线性 DP 运用题_算法_05出发,最后到达741. 摘樱桃 : 经典线性 DP 运用题_后端_06,只能向下或向右走,并且只能穿越有效的格子(即只可以穿过值为741. 摘樱桃 : 经典线性 DP 运用题_算法_02或者741. 摘樱桃 : 经典线性 DP 运用题_初始化_03
  • 当到达741. 摘樱桃 : 经典线性 DP 运用题_后端_06后,你要继续走,直到返回到741. 摘樱桃 : 经典线性 DP 运用题_算法_05
  • 当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为0);
  • 如果在741. 摘樱桃 : 经典线性 DP 运用题_算法_05741. 摘樱桃 : 经典线性 DP 运用题_后端_06

示例 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​​​ 是一个741. 摘樱桃 : 经典线性 DP 运用题_初始化的二维数组,N的取值范围是741. 摘樱桃 : 经典线性 DP 运用题_算法_14
  • 每一个​​grid[i][j]​​​ 都是集合​​{-1, 0, 1}​​ 其中的一个数
  • 可以保证起点​​grid[0][0]​​​ 和终点​​grid[N-1][N-1]​​​ 的值都不会是741. 摘樱桃 : 经典线性 DP 运用题_算法_04

线性 DP

为了方便,我们令 ​​grid​​​ 为 ​​g​​​,同时调整矩阵横纵坐标从 741. 摘樱桃 : 经典线性 DP 运用题_初始化_03

原问题为先从左上角按照「只能往下 + 只能往右」的规则走到右下角,然后再按照「只能往上 + 只能往左」的规则走回左上角,途径的值为 741. 摘樱桃 : 经典线性 DP 运用题_初始化_03 的格子得一分(只能得分一次,得分后置零),同时不能经过值为 741. 摘樱桃 : 经典线性 DP 运用题_算法_04

其中第二趟的规则等价于按照第一趟的规则从左上角到右下角再走一遍,再结合每个位置的只能得分一次,可以将原问题等价于:两个点从左上角开始同时走,最终都走到右下角的最大得分。

定义 741. 摘樱桃 : 经典线性 DP 运用题_Java_19 为当前走了 741. 摘樱桃 : 经典线性 DP 运用题_Java_20 步(横纵坐标之和),且第一个点当前在第 741. 摘樱桃 : 经典线性 DP 运用题_复杂度_21 行,第二点在第 741. 摘樱桃 : 经典线性 DP 运用题_Java_22 行时的最大得分,最终答案为 741. 摘樱桃 : 经典线性 DP 运用题_初始化_23,同时有 741. 摘樱桃 : 经典线性 DP 运用题_初始化_24

由于两个点是同时走(都走了 741. 摘樱桃 : 经典线性 DP 运用题_Java_20 步),结合「只能往下 + 只能往右」的规则,可直接算得第一个点所在的列为 741. 摘樱桃 : 经典线性 DP 运用题_复杂度_26,第二点所在的列为 741. 摘樱桃 : 经典线性 DP 运用题_算法_27

不失一般性考虑 741. 摘樱桃 : 经典线性 DP 运用题_Java_19 该如何转移,两个点均有可能走行或走列,即有 741. 摘樱桃 : 经典线性 DP 运用题_Java_29 种前驱状态:741. 摘樱桃 : 经典线性 DP 运用题_后端_30741. 摘樱桃 : 经典线性 DP 运用题_Java_31741. 摘樱桃 : 经典线性 DP 运用题_算法_32741. 摘樱桃 : 经典线性 DP 运用题_算法_33,在四者中取最大值,同时当前位置 741. 摘樱桃 : 经典线性 DP 运用题_Java_34741. 摘樱桃 : 经典线性 DP 运用题_后端_35 的得分需要被累加,假设两者得分别为 741. 摘樱桃 : 经典线性 DP 运用题_Java_36741. 摘樱桃 : 经典线性 DP 运用题_复杂度_37,若两个位置不重叠的话,可以同时累加,否则只能累加一次。

一些细节:为了防止从值为 741. 摘樱桃 : 经典线性 DP 运用题_算法_04 的格子进行转移影响正确性,我们需要先将所有 741. 摘樱桃 : 经典线性 DP 运用题_Java_19

代码:

class Solution {
static int N = 55, INF = Integer.MIN_VALUE;
static int[][][] f = new int[2 * N][N][N];
public int cherryPickup(int[][] g) {
int n = g.length;
for (int k = 0; k <= 2 * n; k++) {
for (int i1 = 0; i1 <= n; i1++) {
for (int i2 = 0; i2 <= n; i2++) {
f[k][i1][i2] = INF;
}
}
}
f[2][1][1] = g[0][0];
for (int k = 3; k <= 2 * n; k++) {
for (int i1 = 1; i1 <= n; i1++) {
for (int i2 = 1; i2 <= n; i2++) {
int j1 = k - i1, j2 = k - i2;
if (j1 <= 0 || j1 > n || j2 <= 0 || j2 > n) continue;
int A = g[i1 - 1][j1 - 1], B = g[i2 - 1][j2 - 1];
if (A == -1 || B == -1) continue;
int a = f[k - 1][i1 - 1][i2], b = f[k - 1][i1 - 1][i2 - 1], c = f[k - 1][i1][i2 - 1], d = f[k - 1][i1][i2];
int t = Math.max(Math.max(a, b), Math.max(c, d)) + A;
if (i1 != i2) t += B;
f[k][i1][i2] = t;
}
}
}
return f[2 * n][n][n] <= 0 ? 0 : f[2
  • 时间复杂度:状态数量级为741. 摘樱桃 : 经典线性 DP 运用题_复杂度_40,每个状态转移复杂度为741. 摘樱桃 : 经典线性 DP 运用题_算法_41。整体复杂度为741. 摘樱桃 : 经典线性 DP 运用题_复杂度_42
  • 空间复杂度:741. 摘樱桃 : 经典线性 DP 运用题_复杂度_42

最后

这是我们「刷穿 LeetCode」系列文章的第 ​​No.741​​ 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:​​github.com/SharingSour…​​ 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。