题目链接:https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/
1. 题目介绍(47. 礼物的最大价值)
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
以下面的棋盘为例,如果沿着带下划线的数字的线路(1、12、5、7、7、16、5),那么我们能拿到最大价值为 53 的礼物。
【测试用例】:
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
【条件约束】:
提示:
- 0 < grid.length <= 200
- 0 < grid[0].length <= 200
2. 题解
2.1 动态规划(原书题解)-- O(mn)
时间复杂度O(mn),空间复杂度O(mn)
【解题思路】:
本题是一个典型的动态规划解题的问题,我们先用递归的思路进行分析。我们先定义第一个函数f(i,j)
表示到达坐标为(i, j)
的格子时能拿到的礼物总和的最大值。根据题目要求,我们有两种可能的途径到达坐标为(i, j)
的格子:
- 通过格子
(i-1,j)
– 向下- 通过格子
(i, j-1)
– 向右……
所以f(i,j) = max(f(i-1,j), f(i,j-1)) + grid[i][j];
……
【实现策略】:
-
……
下列解法,我们是对dp
数组中的第一行和第一列进行了分开赋值,我们可以通过定义两个变量充当临时存储的作用,将循环合在一起,这样就不用像下列解法一样,将循环分为三部分了。
……
【进一步优化】:
由于到达坐标为(i,j)
的格子时能够拿到的礼物的最大价值只依赖坐标为(i-1,j)
和(i,j-1)
的两个格子,因此第i-2行
及更上面的格子礼物的最大价值实际上没有必要保存下来。
……
因此我们可以使用一个一维数组来替代下列代码中的二维数组dp
。扫描二维码关注公众号,回复: 14731813 查看本文章
class Solution {
// Solution1:动态规划
public int maxValue(int[][] grid) {
// 无效输入判断
int m = grid.length;
int n = grid[0].length;
if (m <= 0 || n <= 0) return 0;
// 定义二维数组,存储计算结果
int[][] dp = new int[m][n];
// 为数组第一行赋值
dp[0][0] = grid[0][0];
for (int i = 1; i < n; i++) dp[0][i] = dp[0][i-1] + grid[0][i];
// 为数组的第一列赋值
for (int j = 1; j < m; j++) dp[j][0] = dp[j-1][0] + grid[j][0];
for (int i = 1; i < m; i++){
for (int j = 1; j < n; j++)
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]) + grid[i][j];
}
return dp[m-1][n-1];
}
}
【优化思路】:用一维数组来替代二维数组
举个例子,在计算f[1][1]
时,会用到f[0][1]
,但是之后就不再用到了。那么干脆把f[1][1]
记到f[0][1]
中,这样对于f[1][2]
来说,它需要的数据就在f[0][1]
和f[0][2]
中。f[1][2]
算完后也可以同样记到f[0][2]
中。所以只需要一个长为n+1
的一维数组就够了。
……
优化之后,时间复杂度不变,空间复杂度变为O(n)
如上图所示,通过计算,存储,覆盖的形式,我们就可以将上图中的二维数组改为一维数组实现。
class Solution {
// Solution2:动态规划(二维改一维)
public int maxValue(int[][] grid) {
// 无效输入判断
int m = grid.length;
int n = grid[0].length;
if (m <= 0 || n <= 0) return 0;
// 使用一维数组替代二维数组
int[] dp = new int[n];
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++) {
// 初始化辅助存值变量
int up = 0;
int left = 0;
// i > 0 表示第一行数据存在
if (i > 0) up = dp[j];
// j > 0 表示第一列数据存在
if (j > 0) left = dp[j-1];
dp[j] = Math.max(up, left) + grid[i][j];
}
}
return dp[n-1];
}
}