剑指 Offer 47. 礼物的最大价值

解题思路:动态规划

动态规划的解题步骤:

  1. 状态定义
  2. 确定状态转移方程与初始值
  3. 递推实现

状态定义

我们定义 dp 矩阵,dp(i,j) 代表从棋盘的左上角开始到达单元格 (i,j) 时能够拿到礼物的最大累计价值。

确定状态转移方程与初始值
  • i = 0 , j = 0 时,此时我们只能拿到 grid[0][0] 的礼物,所以累计价值为:dp[0][0] = grid[0][0]
  • i = 0 , j ≠ 0 时,也就是对应矩阵的第一行,我们只能从左边到达
  • i ≠ 0 , j = 0 时,也就是对应矩阵的第一列,我们只能从上边到达
  • i ≠ 0 , j ≠ 0 时,可以从左边或上边到达

我们可以总结出状态转移方程:

image.png

代码

  1. class Solution {
  2. public int maxValue(int[][] grid) {
  3. int m = grid.length;
  4. int n = grid[0].length;
  5. int[][] dp = new int[m][n];
  6. dp[0][0] = grid[0][0];
  7. for(int i = 1; i < grid[0].length; i++){
  8. dp[0][i] = grid[0][i] + dp[0][i - 1];
  9. }
  10. for(int i = 1; i < grid.length; i++){
  11. dp[i][0] = grid[i][0] + dp[i - 1][0];
  12. }
  13. for(int i = 1; i < grid.length; i++){
  14. for(int j = 1; j < grid[0].length; j++){
  15. dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]) + grid[i][j];
  16. }
  17. }
  18. return dp[m - 1][n - 1];
  19. }
  20. }

空间复杂度的优化:

上述代码中,我们新开辟了矩阵 dp 定义到达每个格子时礼物的累计最大值,很显然这浪费了 O(MN) 的额外空间。

我们可以做原地 dp,即:在原数组上进行修改,以达到节省空间的目的。

代码如下:

  1. class Solution {
  2. public int maxValue(int[][] grid) {
  3. for(int i = 1; i < grid[0].length; i++){
  4. grid[0][i] += grid[0][i - 1];
  5. }
  6. for(int i = 1; i < grid.length; i++){
  7. grid[i][0] += grid[i - 1][0];
  8. }
  9. for(int i = 1; i < grid.length; i++){
  10. for(int j = 1; j < grid[0].length; j++){
  11. grid[i][j] += Math.max(grid[i - 1][j],grid[i][j - 1]);
  12. }
  13. }
  14. return grid[grid.length - 1][grid[0].length - 1];
  15. }
  16. }

复杂度分析

  • 时间复杂度:O(MN)

M,N 分别表示矩阵的行高与列宽,我们需要遍历整个矩阵,所以时间复杂度为 O(MN)

  • 空间复杂度:O(1)

原地 dp 使用了常数大小的额外空间