[LeetCode] 64. Minimum Path Sum 最小路径和
Given a m x n
grid
filled with non-negative numbers, find a path from top left to bottom right, which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.
Example 1:
Input: grid = [[1,3,1],[1,5,1],[4,2,1]] Output: 7 Explanation: Because the path 1 → 3 → 1 → 1 → 1 minimizes the sum.
Example 2:
Input: grid = [[1,2,3],[4,5,6]] Output: 12
Constraints:
m == grid.length
n == grid[i].length
1 <= m, n <= 200
0 <= grid[i][j] <= 200
这道题给了我们一个只有非负数的二维数组,让找一条从左上到右下的路径,使得路径和最小,限定了每次只能向下或者向右移动。一个常见的错误解法就是每次走右边或下边数字中较小的那个,这样的贪婪算法获得的局部最优解不一定是全局最优解,因此是不行的。实际上这道题跟之前那道 Dungeon Game 没有什么太大的区别,都需要用动态规划 Dynamic Programming 来做,这应该算是 DP 问题中比较简单的一类,我们维护一个大小为 m+1 by n+1 的二维的 dp 数组,其中 dp[i][j] 表示到达位置 (i-1, j-1) 的最小路径和。接下来找状态转移方程,因为到达某个位置 (i, j) 只有两种情况,要么从上方 (i-1, j) 过来,要么从左边 (i, j-1) 过来,这里选择 dp 值较小的那个路径,即比较 dp[i-1][j] 和 dp[i][j-1],将其中的较小值加上当前的数字 grid[i-1][j-1](注意这里的坐标转换),就是当前位置的 dp 值了。由于 dp 数组的大小比原数组大,且都初始化为了整型最大值,而 dp[1][1] 对应的是数组中的起始位置,根据状态转移方程,其得加上 dp[0][1] 和 dp[1][0] 中的较小值,所以这两个值至少要有一个是0,也可以都赋值为0,这样才能得到正确的结果,代码如下:
解法一:
class Solution { public: int minPathSum(vector<vector<int>>& grid) { int m = grid.size(), n = grid[0].size(); 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] = grid[i - 1][j - 1] + min(dp[i - 1][j], dp[i][j - 1]); } } return dp[m][n]; } };
我们可以优化空间复杂度,可以使用一个一维的 dp 数组就可以了,初始化为整型最大值,但是 dp[0] 要初始化为0。之所以可以用一维数组代替之前的二维数组,是因为当前的 dp 值只跟左边和上面的 dp 值有关。这里并不提前更新第一行或是第一列,而是在遍历的时候判断,若j等于0时,说明是第一列,直接加上当前的数字,否则就要比较是左边的 dp[j-1] 小还是上面的 dp[j] 小,当是第一行的时候,dp[j] 是整型最大值,所以肯定会取到 dp[j-1] 的值,然后再加上当前位置的数字即可,参见代码如下:
解法二:
class Solution { public: int minPathSum(vector<vector<int>>& grid) { int m = grid.size(), n = grid[0].size(); vector<int> dp(n, INT_MAX); dp[0] = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (j == 0) dp[j] += grid[i][j]; else dp[j] = grid[i][j] + min(dp[j], dp[j - 1]); } } return dp[n - 1]; } };
我们还可以进一步的优化空间,连一维数组都不用新建,而是直接使用原数组 grid 进行累加,这里的累加方式跟解法一稍有不同,没有提前对第一行和第一列进行赋值,而是放在一起判断了,当i和j同时为0时,直接跳过。否则当i等于0时,只加上左边的值,当j等于0时,只加上面的值,否则就比较左边和上面的值,加上较小的那个即可,参见代码如下:
解法三:
class Solution { public: int minPathSum(vector<vector<int>>& grid) { for (int i = 0; i < grid.size(); ++i) { for (int j = 0; j < grid[i].size(); ++j) { if (i == 0 && j == 0) continue; if (i == 0) grid[0][j] += grid[0][j - 1]; else if (j == 0) grid[i][0] += grid[i - 1][0]; else grid[i][j] += min(grid[i - 1][j], grid[i][j - 1]); } } return grid.back().back(); } };
下面这种写法跟上面的基本相同,只不过用了 up 和 left 两个变量来计算上面和左边的值,看起来稍稍简洁一点,参见代码如下:
解法四:
class Solution { public: int minPathSum(vector<vector<int>>& grid) { for (int i = 0; i < grid.size(); ++i) { for (int j = 0; j < grid[i].size(); ++j) { if (i == 0 && j == 0) continue; int up = (i == 0) ? INT_MAX : grid[i - 1][j]; int left = (j == 0) ? INT_MAX : grid[i][j - 1]; grid[i][j] += min(up, left); } } return grid.back().back(); } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/64
类似题目:
Minimum Path Cost in a Grid
Maximum Number of Points with Cost
Minimum Cost Homecoming of a Robot in a Grid
Paths in Matrix Whose Sum Is Divisible by K
Check if There is a Path With Equal Number of 0's And 1's
Minimum Cost of a Path With Special Roads
参考资料:
https://leetcode.com/problems/minimum-path-sum/
https://leetcode.com/problems/minimum-path-sum/discuss/23457/C%2B%2B-DP
https://leetcode.com/problems/minimum-path-sum/discuss/23617/C%2B%2B-solution-beat-98.59
https://leetcode.com/problems/minimum-path-sum/discuss/23611/My-Java-clean-code-DP-no-extra-space