题目描述
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明: 每次只能向下或者向右移动一步。
示例
示例 1:
输入: grid = [[1,3,1],[1,5,1],[4,2,1]]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
条件约束
m == grid.lengthn == grid[i].length1 <= m, n <= 2000 <= grid[i][j] <= 200
题目描述重点划词
左上角右下角数字总和为最小
思考
从题目描述的关键词中不难看出这就是一个动态规划的题目,没有一点技巧可言。从左上角到右下角每走一步都要挑选最优的路径,局部最优+其他最优 == 全局最优影响到达 [i][j] 的花费只有两个点有关,分别是点上方和点左边,ok 动态规划转移方程如喝水一样简单
dp[i][j] = cost[i][j] + max(dp[i-1][j], dp[i][j-1])
其中有个问题边界怎么搞,因为 [0][j] 没有上方的的点 [i][0] 没有左边的点,思考中...嗯好像很简单,到达它们这些点的路径路数只有一条最上方的点只能从左边过来,最左边的点只能从上方过来,所以 dp[0][j] == cost[0][j] + dp[0][j-1]、dp[i][0] == cost[i][0] + dp[i-1][0]
下面是AreaZer同学给出的示例代码
func minPathSum(grid [][]int) int {
m, n := len(grid), len(grid[0])
dp := make([][]int, m)
for i := range dp {
dp[i] = make([]int, n)
}
dp[0][0] = grid[0][0] // 右上角
// 最左边一列
for i := 1; i < m; i++ {
dp[i][0] = dp[i-1][0] + grid[i][0]
}
// 最上面那一行
for j := 1; j < n; j++ {
dp[0][j] = dp[0][j-1] + grid[0][j]
}
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
}
}
return dp[m-1][n-1]
}
啊?这... 内存占用被甩一大截,肯定我还有做的不好的地方,那我想想,o O 哦!其实我可以不用开辟 dp 额外空间,从 for 循环看 grid[i][j] 都只是利用了一次而且数组大小和我的 dp 一模一样,我是不是可以直接使用 grid,直接在基础上改,ok,我先试试
func minPathSum2(grid [][]int) int {
m, n := len(grid), len(grid[0])
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if i+j == 0 { // 右上角
continue
}
if i == 0 { // 最上面那一行
grid[i][j] += grid[i][j-1]
continue
}
if j == 0 { // 最左边一列
grid[i][j] += grid[i-1][j]
continue
}
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
}
}
return grid[m-1][n-1]
}
啊?这用时怎么还边长了,这...难道是我的 if 太多了优雅的代码变成了石(si)山,if 语句大多数都可以用 switch case 去优化,好吧那我来试试
func minPathSum3(grid [][]int) int {
m, n := len(grid), len(grid[0])
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
switch i + j {
case 0: // i == 0 && j == 0
continue
case i: // j == 0
grid[i][j] += grid[i-1][j]
case j: // i == 0
grid[i][j] += grid[i][j-1]
default:
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
}
}
}
return grid[m-1][n-1]
}
我心满意足了,你们满意吗?
题目来源 64. 最小路径和