【剑指offer】47.礼物的最大价值 - 两种解法:DP优化到一维数组、记忆化搜索

题目链接

leetcode链接

acwing链接

题目描述

在一个 m × n m×n m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0
)。

你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。

给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?

注意:

m , n > 0 m,n>0 m,n>0
m × n ≤ 1350 m×n≤1350 m×n1350

样例

输入:
[
  [2,3,1],
  [1,7,1],
  [4,6,1]
]

输出:19

解释:沿着路径 2→3→7→6→1 可以得到拿到最大价值礼物。

题解链接


动态规划

状态表示:
集合:f[i][j]表示走到矩阵i行j列的位置的所有走法
属性:路径中得到礼物的最大值MAX
集合划分:
这道题的条件其实非常的强和简单,因为只能往右和往下走,所以对于i行j列的位置,只能由上面(i-1,j)或者左边(i,j-1)转移而来。
上面:f[i][j] = f[i-1][j] + grid[i][j]
左面:f[i][j] = f[i][j-1] + grid[i][j]
经典例子小明考试。。。先不看共同的grid[i][j],在f[i-1][j], f[i][j-1]里面取较大的那个即可。
具体地得到:f[i][j] = max(f[i-1][j], f[i][j-1]) + grid[i][j]

最后的答案就是走到最右下角的位置的时候dp数值最后一个数值。

下面是一些实现时候考虑的细节:

因为要处理0,所以dp数组里面从1开始计数

这样的话每次处理dp[i][j]对应的数值其实是grid[i-1][j-1]

最上面一行和最左边一列,可以不用特殊处理,因为界外是0,礼物的数值都是正数,取最大值不会取到边界外。

每次状态转移需要的数据是当前格子左上方的,无论是先遍历行还是先遍历列其实都是可以满足左上角的数据被计算过的。

代码里先遍历行。

时间复杂度

需要遍历每个点

O ( m n ) O(mn) O(mn), n n n m m m是行数和列数

C++ 代码

class Solution {
    
    
public:
    int getMaxValue(vector<vector<int>>& grid) {
    
    
        // n是行数,m是列数
        int n = grid.size(), m = grid[0].size();
        // 创建dp数组时候多一行一列
        vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
        // 遍历dp数组,和grid中的坐标是对应的
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                // dp的[i][j]对应的位置是grid中的grid[i-1][j-1]
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i-1][j-1];
        return dp[n][m];
    }
};

考虑到每次状态转移都只用到了上一行的数据,可以优化到一维滚动数组里面。去掉行的维度i

每次要用到的是左侧的数据,每次要保证左侧的数据被更新过了,所以j从小到大进行更新。

class Solution {
    
    
public:
    int getMaxValue(vector<vector<int>>& grid) {
    
    
        int n = grid.size(), m = grid[0].size();
        vector<int> dp(m+1, 0);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                // 这里的dp[j]是更新前的,对应dp[i-1][j]
                // dp[j-1]已经更新过了,对应dp[i][j-1]
                dp[j] = max(dp[j], dp[j-1]) + grid[i-1][j-1];
        return dp[m];
    }
};

记忆化搜索

DP通常可以写作记忆化搜索的形式。

用一个数组来记录,这个数组的内容含义和前面的DP是一致的。

memory[i][j]表示走到矩阵i行j列的位置的所有走法

用-1表示没有被搜索过的位置(被搜索过的话就会更新成对应的数值)

复杂度

每个点计算一次,所以和前面的一样。

O ( m n ) O(mn) O(mn), n n n m m m是行数和列数。

空间复杂度相对高一些,最高需要 ( m + n ) (m+n) (m+n)级别的函数递归调用以外。需要 O ( m n ) O(mn) O(mn)的记忆存储空间,取较大的是 O ( m n ) O(mn) O(mn)

C++ 代码

class Solution {
    
    
private:
    vector<vector<int>> memory;
    int ms(vector<vector<int>>& grid, int x, int y)
    {
    
    
        // 如果已经被搜索过,memory[x][y]就是走到xy位置的礼物最大值了,直接返回
        if(memory[x][y]!=-1) return memory[x][y];
        // 如果没有更新过数值,则计算,用搜索代替在数组中寻找数值,可以递归下去
        memory[x][y] = max(ms(grid, x-1, y), ms(grid, x, y-1)) + grid[x-1][y-1];
        return memory[x][y];
    }
public:
    int getMaxValue(vector<vector<int>>& grid) {
    
    
        int n = grid.size(), m = grid[0].size();
        memory = vector<vector<int>>(n+1, vector<int>(m+1, -1));
        // 初始化,第一行和第一列都是0
        for(int i=0;i<=n;i++) memory[i][0] = 0;
        for(int j=0;j<=m;j++) memory[0][j] = 0;
        return ms(grid, n, m);
    }
};

猜你喜欢

转载自blog.csdn.net/YanHS_/article/details/130631676