原题链接: https://leetcode.com/problems/minimum-path-sum/
1. 题目介绍
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:
Input:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.
给出一个 m 乘以 n 的矩阵,矩阵中的数都是非负数。起点是左上角,终点是右下角,只能向下或者向右走。走过的数字加起来求和,求最小的和是多少。
2. 解题思路 – 动态规划
这个题非常适合用动态规划的思路来解题。在说明解法之前,我想总结一下动态规划的常规思路。
动态规划思路关键在于分解和求解子问题,然后根据子问题的解不断递推,得出最终解,并且在每一步的计算中,需要保留计算过的子问题的解,这样当遇到同样的子问题时就不用继续向下求解而直接可以得到结果。
于是我们需要把所有子问题的状态用一个数据结构(数组、链表等等)来存储,这个数据结构可以把每个状态和对应的结果关联起来,当求解子问题时,如果数据结构里面已经有该状态的解就不用再求了,如果没有则继续求,然后存起来。同样每次求解完一个状态的解后也要将其放入数据结构中保存。
以这道题为例 ,我们来看一下如何用动态规划解决最优解问题,以及如何对动态规划进行优化:将二维数组转化为一维数组。
2.1 二维数组
根据上文所述,状态和结果需要进行关联,那么在这道题中,什么是状态,什么是结果呢?
状态: 矩阵中的不同位置,一个位置,就是一个状态
结果:到达某个位置的最小路径和
因此我们可以使用二维数组dp[ i ][ j ] , 代表到达第 i 行、第 j 列的最小路径和(i 和 j 都是从0开始的)。本题的最终结果就是dp[ m-1 ][ n-1 ]的值,即到达第m行,第n列的最小路径和。
由于只能向下或者向右走,因此到达位置(i,j)只能从(i-1,j)或者(i,j-1)而来。
于是递推关系式可以得出:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
+
g
r
i
d
[
i
]
[
j
]
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j]
其中grid[ i ][ j ]是矩阵中,第 i 行,第 j 列的数字,(i 和 j 都是从0开始的)。
当然,位于矩阵第0行的所有位置(0,j)只能从(0,j-1)而来,
位于矩阵第0列的所有位置(i,0)只能从(i-1,0)而来。
而第0行的第0列,也就是我们的起点,它的初值为grid[ 0 ][ 0 ]
具体实现代码如下:
实现代码
class Solution {
public int minPathSum(int[][] grid) {
int length = grid.length;
if(length == 0) {
return 0;
}
int width = grid[0].length;
int [][] dp = new int [length][width];
dp[0][0] = grid[0][0];
for(int i = 0;i<length;i++) {
for(int j = 0;j<width;j++) {
if(i ==0 && j ==0) {
continue;
}
if(i == 0) {
dp[i][j] = dp[i][j-1] + grid[i][j];
}
else if(j == 0) {
dp[i][j] = dp[i-1][j] + grid[i][j];
}
else {
dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
}
return dp[length-1][width-1];
}
}
2.2 一维数组
再来看上面解法的递推关系式:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
+
g
r
i
d
[
i
]
[
j
]
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j]
每次在更新dp[ i ][ j ]的时候,只用到了二维数组中的 dp[ i-1 ][ j ] , dp[ i ][ j-1 ],其他的值都没有用到。分配的空间没有得到充分利用。
那么我们可以使用一维数组代替原来的二维数组,每次更新dp[ j ]的时候,dp[ j ]的值就已经是原来二维数组中dp[ i-1 ][ j ] 的值,dp[ j-1 ]的值就是原来二维数组中dp[ i ][ j-1 ] 的值。然后选取dp[ j ] 和dp [j-1]中较小的一个加grid[ i ][ j ]就可以了。
实现代码
class Solution {
public int minPathSum(int[][] grid) {
int length = grid.length;
if(length == 0) {
return 0;
}
int width = grid[0].length;
int [] dp = new int [width];
dp[0] = grid[0][0];
for(int i = 1 ; i<width ; i++) {
dp[i] = dp[i-1] + grid[0][i];
}
for(int i = 1; i<length ;i++) {
for(int j = 0 ; j < width ; j++) {
if(j == 0) {
dp[j] += grid[i][j];
}
else {
dp[j] = Math.min(dp[j], dp[j-1]) + grid[i][j];
}
}
}
return dp[width-1];
}
}