和这一题特别像:LeetCode第 120 题:三角形最小路径和(C++)_qq_32523711的博客-CSDN博客)
动态规划典型题目,状态方程如下
非边界,当i、j 均不为0时:
f [ i ] [ j ] = m i n ( f [ i ] [ j − 1 ] , f [ i − 1 ] [ j ] ) + g r i d [ i ] [ j ] f[i][j] = min(f[i][j-1], f[i-1][j]) + grid[i][j] f[i][j]=min(f[i][j−1],f[i−1][j])+grid[i][j]
f [ 0 ] [ 0 ] = g r i d [ 0 ] [ 0 ] f[0][0] = grid[0][0] f[0][0]=grid[0][0]
边界时(左边界j=0,上边界i=0)
f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] + g r i d [ i ] [ 0 ] f[i][0] = f[i-1][0] + grid[i][0] f[i][0]=f[i−1][0]+grid[i][0]
f [ 0 ] [ i ] = f [ 0 ] [ i − 1 ] + g r i d [ 0 ] [ i ] f[0][i] = f[0][i - 1] + grid[0][i] f[0][i]=f[0][i−1]+grid[0][i]
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int row = grid.size();
int col = grid[0].size();
vector<vector<int>> f(row, vector<int>(col, 0));
f[0][0] = grid[0][0];
for (int i = 1; i < row; ++i) //左边界
f[i][0] = f[i - 1][0] + grid[i][0];
for (int i = 1; i < col; ++i) //上边界
f[0][i] = f[0][i - 1] + grid[0][i];
for (int i = 1; i < row; ++i){ //其他
for (int j = 1; j < col; ++j)
f[i][j] = min(f[i][j - 1], f[i - 1][j]) + grid[i][j];
}
return f[row - 1][col - 1];
}
};
换个写法,思路更加清晰:
第一次循环,根据第一行的左边界f[0][0](已知)计算出第一行的剩余元素;
剩下的每一次循环之前,先计算当前行的左边界元素,然后根据左边界元素和上一行元素,计算出当前行的剩余元素。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int row = grid.size();
int col = grid[0].size();
vector<vector<int>> f(row, vector<int>(col, 0));
f[0][0] = grid[0][0];
for (int i = 0; i < row; ++i){//先考虑行
if(i != 0) f[i][0] = f[i-1][0] + grid[i][0]; //计算当前行的左边界元素
for (int j = 1; j < col; ++j){
if(i == 0) //如果是上边界(第一行)
f[0][j] = f[0][j - 1] + grid[i][j];
else
f[i][j] = min(f[i][j - 1], f[i - 1][j]) + grid[i][j];
}
}
return f[row - 1][col - 1];
}
};
可以看到空间复杂度为O(m*n),为辅助数组的占用空间。
下面进行一下优化吧,由状态方程可以看到,每一行的元素值只与该行元素和上一行元素有关,所以其实不需要n x m的额外空间(和这个类似:LeetCode第 120 题:三角形最小路径和(C++)_qq_32523711的博客-CSDN博客)。
一般这种情况我们可以使用滚动数组减少空间消耗,两行(2xn)交替使用就可以,更甚,其实一行(1xn)就可以。
直接考虑一行(1xn),代码:
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int row = grid.size();
int col = grid[0].size();
vector<int> f(col, 0);
for (int i = 0; i < row; ++i){
f[0] = f[0] + grid[i][0]; //左边界f[0](第一列)
for (int j = 1; j < col; ++j){
if(i == 0) //如果是上边界(第一行)
f[j] = f[j-1] + grid[i][j];
else
f[j] = min(f[j - 1], f[j]) + grid[i][j];
}
}
return f[col - 1];
}
};
滚动数组(2xn)的也写一下:
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int row = grid.size();
int col = grid[0].size();
vector<vector<int>> f(2, vector<int>(col, 0));
f[0][0] = grid[0][0];
int cur; //滚动数组下标
int pre; //滚动数组下标
for (int i = 0; i < row; ++i){//先考虑行
cur = i % 2; //要么1要么0)
pre = 1-cur; //要么0要么1
if(i != 0) f[cur][0] = f[pre][0] + grid[i][0]; //计算当前行的左边界元素
for (int j = 1; j < col; ++j){
if(i == 0) //如果是上边界(第一行)
f[cur][j] = f[cur][j - 1] + grid[i][j];
else
f[cur][j] = min(f[cur][j - 1], f[pre][j]) + grid[i][j];
}
}
return f[cur][col - 1];
}
};
更奇葩的也还有,空间复杂度O(1),直接在原数组上修改就行(因为每个数只会用到一次),思路和第一段代码一样,只是存储的时候稍作修改即可。。。不过,个人不是很推荐,修改原数组毕竟不太好。。吧。。。
这个题使用回溯的思路也是可以的,而且剪枝也简单(路径和大于当前最小路径和就直接cut掉)