给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧的任意单元格(不必相邻)。从值为 c1 的单元格移动到值为 c2 的单元格的得分为 c2 - c1 。
你可以从 任一 单元格开始,并且必须至少移动一次。
返回你能得到的 最大 总得分。
示例 1:
输入:grid = [[9,5,7,3],[8,9,6,1],[6,7,14,3],[2,5,3,1]]
输出:9
解释:从单元格 (0, 1) 开始,并执行以下移动:
- 从单元格 (0, 1) 移动到 (2, 1),得分为 7 - 5 = 2 。
- 从单元格 (2, 1) 移动到 (2, 2),得分为 14 - 7 = 7 。
总得分为 2 + 7 = 9 。
示例 2:
输入:grid = [[4,3,2],[3,2,1]]
输出:-1
解释:从单元格 (0, 0) 开始,执行一次移动:从 (0, 0) 到 (0, 1) 。得分为 3 - 4 = -1 。
动态规划+前缀最小值
cpp
class Solution {
public:
int maxScore(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<vector<int>> f(m+1, vector<int>(n+1, INT_MAX));
int ans = INT_MIN;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
int mn = min(f[i+1][j],f[i][j+1]);
ans = max(ans, grid[i][j] - mn);
f[i+1][j+1] = min(mn, grid[i][j]);
}
}
return ans;
}
};
时间复杂度 :O(mn),其中 m 和 n 分别为 mat 的行数和列数。
空间复杂度:O(mn)。
这道题首先要化简问题,示例1中:
从单元格 (0, 1) 移动到 (2, 1),得分为 7 - 5 = 2 。
从单元格 (2, 1) 移动到 (2, 2),得分为 14 - 7 = 7 。
7-5 + 14 - 7 = 9,9是总得分。可以发现,计算总得分只需要终点14和起点5,通过14-5就可以得到。那么就说明,这道题就有了个初始的思路,遍历网格每个元素,然后通过某个容器储存这个元素为右下角(终点)的子矩阵中,子矩阵左上角(也就是起点)的最小值是多少。
所以在每次遍历元素的时候,我们定义一个局部变量mn,int mn = min(f[i+1][j],f[i][j+1]);
,f[i+1][j+1]的含义是左上角为(0,0),右下角为(i,j)的子矩阵的最小值。因为在我们遍历到某个元素时,他的路径要么从左边来,也就是f[i+1][j]
,要么从上面来,也就是f[i][j+1]
,也就是说,当grid[i][j]为终点的时候,要在以(0,0)为左上角,(i,j)为右下角的矩阵中(不包括(i,j))找到最小的那个元素值mn。然后将grid[i][j] - mn的值来更新ans。最后更新f[i+1][j+1],如果grid[i][j]比mn还小的话,那么就其更新为grid[i][j]。
优化:维护列
cpp
class Solution {
public:
int maxScore(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<int> col_min(n, INT_MAX);
int ans = INT_MIN;
for(int i = 0; i < m; i++){
int pre_min = INT_MAX;
for(int j = 0; j < n; j++){
ans = max(ans, grid[i][j] - min(pre_min, col_min[j]));
col_min[j] = min(col_min[j], grid[i][j]);
pre_min = min(pre_min, col_min[j]);
}
}
return ans;
}
};
时间复杂度 :O(mn),其中 m 和 n 分别为 mat 的行数和列数。
空间复杂度:O(n)。
整体思路和第一种方法一样,维护列运用到了滚动数组的思路。我们只需要维护列,也就是矩阵每一列的最小值,然后在遍历的过程中,不断更新列的最小值。
首先我们在第一种方法中,在遍历某一格元素时,我们需要的信息是左上角是(0,0),右下角是(i,j)的矩阵的最小元素值。如果用列来表示我们需要的信息,也就是从0到j-1列的最小值,然后第j列的从0到i-1行的最小值,这两个最小值我们进行比较,更小的那个就是我们需要的信息。在代码中表示min(pre_min, col_min[j])
。
所以说在遍历每一行的时候,我们都要重置pre_min为INT_MAX,pre_min的作用就是找到从0到j-1列的最小值,col_min的作用就是储存第j列的从0到i-1行的最小值。
cpp
ans = max(ans, grid[i][j] - min(pre_min, col_min[j]));
我们可以更新ans的值。
计算完ans后,就要更新pre_min的值为接下来的运算做准备,更新pre_min又需要更新col_min。在更新之前,col_min[j]记载的是第j列的从0到上一行的最小值,更新后就是第j列从0到这一行的最小值。然后知道col_min后,由于pre_min的作用就是找到从0到j-1列的最小值,所以我们pre要更新到第j列,才能供j+1列使用。所以我们只需要比较第j列的最小值有没有比0到j-1列的最小值更小,如果更小的话,那么就更新pre_min的值为该值。
最后遍历完所有元素后,返回ans。