64. 最小路径和https://leetcode.cn/problems/minimum-path-sum/description/
给定一个包含非负整数的m x n网格grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
- 输入:grid = [[1,3,1],[1,5,1],[4,2,1]],输出:7,解释:因为路径1→3→1→1→1的总和最小。
- 输入:grid = [[1,2,3],[4,5,6]],输出:12。
提示:m == grid.length,n == grid[i].length;1 <= m, n <= 200,0 <= grid[i][j] <= 200。
我们用动态规划的思想来解决这个问题。
确定状态表示:根据经验和题目要求,我们用dp[i][j]表示:到达[i, j]位置处,最小路径和是多少。
推导状态转移方程:要想到达[i, j]位置,只有2种情况:
- 先到达[i - 1, j]位置,再向下走一步,到达[i, j]位置。此时的最小路径和就等于,到达[i - 1, j]位置的最小路径和加上网格中[i, j]位置的值,即dp[i - 1][j] + grid[i][j]。
- 先到达[i, j - 1]位置,再向右走一步,到达[i, j]位置。此时的最小路径和就等于,到达[i, j - 1]位置的最小路径和加上网格中[i, j]位置的值,即dp[i][j - 1] + grid[i][j]。
到达[i, j]位置的最小路径和,应该等于这两种情况的较小值,即dp[i][j] = min(dp[i - 1][j] + grid[i][j], dp[i][j - 1] + grid[i][j]) = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]。
初始化:观察状态转移方程,我们发现,在计算dp表最上面一行和最左边一列的值时,会出现越界访问,所以我们要对其初始化。这里我们用增加辅助结点的方式来初始化。我们在dp表的最上面和最左边分别加上一行一列辅助结点。接着,我们要考虑,如何初始化辅助结点,才能确保后续的填表是正确的。我们先把此时的dp表画出来:
* * * *
* ? ? ?
* ?
不难意识到,左上角的?位置的值就应该是网格左上角的值。再根据状态转移方程,我们只需要让min(dp[i - 1][j], dp[i][j - 1])这一项等于0,就能符合预期。所以,我们只需要让左上角的?位置,它的上面和左边2个*位置的值初始化为0即可。
* 0 * *
0 ? ? ?
* ?
接下来考虑除了左上角的?位置之外,其他的?位置的值。为了让这些?位置的值符合预期,我们就要让min(dp[i - 1][j], dp[i][j - 1])这一项中,*位置的值不影响结果。所以,我们要把这些*都初始化为+∞。由于并没有导致溢出风险的运算,用INT_MAX代表+∞即可。
综上所述:我们要在dp表的最上面和最左边分别加上一行一列辅助结点,并且把[0, 1]位置和[1, 0]位置初始化为0,其余辅助结点初始化为INT_MAX。
填表顺序:根据状态转移方程,dp[i][j]依赖于dp[i - 1][j]和dp[i][j - 1]这2项,所以应从上往下,从左往右填表。
返回值:根据状态表示,显然应返回dp表右下角的值,即dp[m][n],对应到达网格右下角的最小路径和。
细节问题:由于新增了一行一列辅助结点,所以dp表的规模比网格的规模大一行一列,即dp表的规模为(m + 1) x (n + 1)。由于添加了辅助结点,要时刻注意下标的映射关系,dp表的[i, j]位置对应网格的[i - 1, j - 1]位置。
时间复杂度:O(m x n),空间复杂度:O(m x n)。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
// 创建dp表
vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
// 初始化
dp[0][1] = dp[1][0] = 0;
// 填表
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
}
}
// 返回结果
return dp[m][n];
}
};