- 题目描述
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物? 示例 1: 输入: [ [1,3,1], [1,5,1], [4,2,1] ] 输出: 12 解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
这道题,第一想法就是用动态规划,也找到了转移方程,但是呢,就是实现的过程有问题,来我们看看题解大佬的清晰分析。
- 解法:动态规划
1.为什么想到用动态规划?
从棋盘的左上角开始拿格子里的礼物,并每次 向右 或者 向下 移动一格、直到到达棋盘的右下角。根据题目说明,易得某单元格只可能从上边单元格或左边单元格到达。
这一步是可以得到的,OK?
看,这是不是就是个动态规划典型情况?动态规划是把一个大问题拆解成一堆小问题,并且将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解。
2.动态规划具有以下性质:
- 【无后效性】“未来与过去无关”,这就是无后效性。
- 【最优子结构】大问题的最优解可以由小问题的最优解推出,这个性质叫做“最优子结构性质”
我们可以定义状态dp,dp(i,j)代表从棋盘左上角开始,到达单元格(i,j)时能拿到礼物的最大累计价值。主要方法是每次通过改变grid单元格元素的值,将grid矩阵当作dp矩阵。(这里参考大佬的题解)
代码
class Solution: def maxValue(self, grid: List[List[int]]) -> int: if not grid: return 0 for i in range(len(grid)): for j in range(len(grid[0])): if i ==0 and j == 0: continue if i == 0: grid[i][j] += grid[i][j-1] elif j ==0: grid[i][j] += grid[i-1][j] else: grid[i][j] += max(grid[i][j-1], grid[i-1][j]) return grid[-1][-1]
时间复杂度O(MN):分别为两个for循环遍历的矩阵长宽
空间复杂度O(1):原地修改grid大小不用额外空间
- 优化
优化的目的是当grid很大,i=0或者j=0的情况仅仅占少数,相当于循环每轮都冗余了一次判断。因此,可先初始化矩阵第一行和第一列,再开始遍历递推。
class Solution: def maxValue(self, grid: List[List[int]]) -> int: if not grid: return 0 m, n = len(grid), len(grid[0]) for j in range(1, n): #初始化第一行 grid[0][j] += grid[0][j-1] for i in range(1, m): #初始化第一列 grid[i][0] += grid[i-1][0] for i in range(1, len(grid)): for j in range(1, len(grid[0])): grid[i][j] += max(grid[i][j-1], grid[i-1][j]) return grid[-1][-1]