Hard
题目描述
给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。grid[i][j] 代表单元格 (i, j) 上的樱桃数量。
你有两个机器人可以为你收集樱桃:
- 机器人 #1 从左上角单元格
(0, 0)出发 - 机器人 #2 从右上角单元格
(0, cols - 1)出发
请你按照如下规则,返回两个机器人可以收集的樱桃数目的最大值:
- 从单元格
(i, j)出发,机器人可以移动到单元格(i + 1, j - 1)、(i + 1, j)或者(i + 1, j + 1) - 当一个机器人经过某个单元格时,它会收集该单元格内所有的樱桃,然后该单元格变成空单元格
- 当两个机器人同时到达同一个单元格时,只有一个机器人可以收集该单元格内的樱桃
- 两个机器人在任意时刻都不能移动到网格外面
- 两个机器人最后都要到达网格最下面一行
示例 1:
输入:grid = [[3,1,1],[2,5,1],[1,5,5],[2,1,1]]
输出:24
解释:机器人 #1 和机器人 #2 的路径用绿色和蓝色分别表示。
机器人 #1 收集的樱桃:(3 + 2 + 5 + 2) = 12
机器人 #2 收集的樱桃:(1 + 5 + 5 + 1) = 12
樱桃总数:12 + 12 = 24
示例 2:
输入:grid = [[1,0,0,0,0,0,1],[2,0,0,0,0,3,0],[2,0,9,0,0,0,0],[0,3,0,5,4,0,0],[1,0,2,3,0,0,6]]
输出:28
提示:
rows == grid.lengthcols == grid[i].length2 <= rows, cols <= 700 <= grid[i][j] <= 100
解题思路
这是一个经典的双路径动态规划问题。我们需要同时考虑两个机器人的移动路径,关键在于将问题转化为状态转移。
核心思路:
状态定义:定义
dp[i][j1][j2]表示在第i行,机器人1在列j1,机器人2在列j2时能收集到的最大樱桃数状态转移:两个机器人都只能向下移动(行数增加1),列数可以变化±1或不变。对于每个状态,我们需要考虑上一行的所有可能位置组合
边界条件:初始状态为
dp[0][0][cols-1] = grid[0][0] + grid[0][cols-1](如果两个机器人在不同位置)重复收集处理:当两个机器人在同一位置时(j1 == j2),樱桃只能被收集一次
优化策略:
- 可以使用滚动数组优化空间复杂度,因为每一行的状态只依赖于上一行
- 每个机器人有3种移动方向,所以总共需要考虑 3×3 = 9 种状态转移
时间复杂度分析:
- 状态数:O(rows × cols²)
- 每个状态的转移:O(9) = O(1)
- 总时间复杂度:O(rows × cols²)
代码实现
class Solution {
public:
int cherryPickup(vector<vector<int>>& grid) {
int rows = grid.size(), cols = grid[0].size();
vector<vector<vector<int>>> dp(rows, vector<vector<int>>(cols, vector<int>(cols, -1)));
// 初始状态
dp[0][0][cols - 1] = grid[0][0] + grid[0][cols - 1];
// 填充dp表
for (int i = 1; i < rows; i++) {
for (int j1 = 0; j1 < cols; j1++) {
for (int j2 = 0; j2 < cols; j2++) {
// 尝试所有可能的前一状态
for (int pj1 = j1 - 1; pj1 <= j1 + 1; pj1++) {
for (int pj2 = j2 - 1; pj2 <= j2 + 1; pj2++) {
if (pj1 >= 0 && pj1 < cols && pj2 >= 0 && pj2 < cols && dp[i - 1][pj1][pj2] != -1) {
int cherries = dp[i - 1][pj1][pj2] + grid[i][j1];
if (j1 != j2) {
cherries += grid[i][j2];
}
dp[i][j1][j2] = max(dp[i][j1][j2], cherries);
}
}
}
}
}
}
// 找到最后一行的最大值
int result = 0;
for (int j1 = 0; j1 < cols; j1++) {
for (int j2 = 0; j2 < cols; j2++) {
result = max(result, dp[rows - 1][j1][j2]);
}
}
return result;
}
};
class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])
dp = [[[-1] * cols for _ in range(cols)] for _ in range(rows)]
# 初始状态
dp[0][0][cols - 1] = grid[0][0] + grid[0][cols - 1]
# 填充dp表
for i in range(1, rows):
for j1 in range(cols):
for j2 in range(cols):
# 尝试所有可能的前一状态
for pj1 in range(max(0, j1 - 1), min(cols, j1 + 2)):
for pj2 in range(max(0, j2 - 1), min(cols, j2 + 2)):
if dp[i - 1][pj1][pj2] != -1:
cherries = dp[i - 1][pj1][pj2] + grid[i][j1]
if j1 != j2:
cherries += grid[i][j2]
dp[i][j1][j2] = max(dp[i][j1][j2], cherries)
# 找到最后一行的最大值
result = 0
for j1 in range(cols):
for j2 in range(cols):
result = max(result, dp[rows - 1][j1][j2])
return result
public class Solution {
public int CherryPickup(int[][] grid) {
int rows = grid.Length, cols = grid[0].Length;
int[,,] dp = new int[rows, cols, cols];
// 初始化为-1表示无效状态
for (int i = 0; i < rows; i++) {
for (int j1 = 0; j1 < cols; j1++) {
for (int j2 = 0; j2 < cols; j2++) {
dp[i, j1, j2] = -1;
}
}
}
// 初始状态
dp[0, 0, cols - 1] = grid[0][0] + grid[0][cols - 1];
// 填充dp表
for (int i = 1; i < rows; i++) {
for (int j1 = 0; j1 < cols; j1++) {
for (int j2 = 0; j2 < cols; j2++) {
// 尝试所有可能的前一状态
for (int pj1 = Math.Max(0, j1 - 1); pj1 <= Math.Min(cols - 1, j1 + 1); pj1++) {
for (int pj2 = Math.Max(0, j2 - 1); pj2 <= Math.Min(cols - 1, j2 + 1); pj2++) {
if (dp[i - 1, pj1, pj2] != -1) {
int cherries = dp[i - 1, pj1, pj2] + grid[i][j1];
if (j1 != j2) {
cherries += grid[i][j2];
}
dp[i, j1, j2] = Math.Max(dp[i, j1, j2], cherries);
}
}
}
}
}
}
// 找到最后一行的最大值
int result = 0;
for (int j1 = 0; j1 < cols; j1++) {
for (int j2 = 0; j2 < cols; j2++) {
result = Math.Max(result, dp[rows - 1, j1, j2]);
}
}
return result;
}
}
var cherryPickup = function(grid) {
const rows = grid.length, cols = grid[0].length;
const dp = Array(rows).fill().map(() =>
Array(cols).fill().map(() => Array(cols).fill(-1))
);
// 初始状态
dp[0][0][cols - 1] = grid[0][0] + grid[0][cols - 1];
// 填充dp表
for (let i = 1; i < rows; i++) {
for (let j1 = 0; j1 < cols; j1++) {
for (let j2 = 0; j2 < cols; j2++) {
// 尝试所有可能的前一状态
for (let pj1 = Math.max(0, j1 - 1); pj1 <= Math.min(cols - 1, j1 + 1); pj1++) {
for (let pj2 = Math.max(0, j2 - 1); pj2 <= Math.min(cols - 1, j2 + 1); pj2++) {
if (dp[i - 1][pj1][pj2] !== -1) {
let cherries = dp[i - 1][pj1][pj2] + grid[i][j1];
if (j1 !== j2) {
cherries += grid[i][j2];
}
dp[i][j1][j2] = Math.max(dp[i][j1][j2], cherries);
}
}
}
}
}
}
// 找到最后一行的最大值
let result = 0;
for (let j1 = 0; j1 < cols; j1++) {
for (let j2 = 0; j2 < cols; j2++) {
result = Math.max(result, dp[rows - 1][j1][j2]);
}
}
return result;
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(rows × cols² × 9) = O(rows × cols²) | 状态数为 rows × cols²,每个状态转移需要考虑 9 种前置状态 |
| 空间复杂度 | O(rows × cols²) | 三维 DP 数组的空间开销,可以优化为 O(cols²) |