题目概述:

题目链接:点我做题
题解
这个题不同于其他动态规划的关键在在于边界条件。
定义f(i,j)为从左上角到下标为
(
i
,
j
)
(i,j)
(i,j)的位置所需的最小路径和。
当
i
=
=
0
i == 0
i==0,也就是说在第0行时,只能通过起始位置往右走,所以
f
(
0
,
j
)
=
∑
k
=
0
j
g
r
i
d
[
0
]
[
k
]
f(0,j) = \sum_{k=0}^{j}{grid[0][k]}
f(0,j)=∑k=0jgrid[0][k],而不是像普通的矩阵动态规划那样
f
(
0
,
j
)
=
g
r
i
d
[
0
]
[
j
]
f(0,j) = grid[0][j]
f(0,j)=grid[0][j]
同理,当
j
=
=
0
j==0
j==0时,也就是说在第0列时,只能从起始位置往下走才能到达,所以
f
(
i
,
o
)
=
∑
k
=
0
i
g
r
i
d
[
k
]
[
0
]
f(i,o) = \sum_{k=0}^{i}{grid[k][0]}
f(i,o)=∑k=0igrid[k][0],而不是像普通的矩阵动态规划那样
f
(
i
,
0
)
=
g
r
i
d
[
i
]
[
0
]
f(i,0) = grid[i][0]
f(i,0)=grid[i][0]
其他情况下,到达
(
i
,
j
)
(i,j)
(i,j)要么是从
(
i
−
1
,
j
)
(i-1,j)
(i−1,j)向右走一步过来的,要么是从
(
i
,
j
−
1
)
(i,j-1)
(i,j−1)向下走一步过来的,到达
(
i
,
j
)
(i,j)
(i,j)的最短路径是到达(i-1,j)的最短路径与到达
(
i
,
j
−
1
)
(i,j-1)
(i,j−1)的最短路径中的较小值与
g
r
i
d
[
i
]
[
j
]
grid[i][j]
grid[i][j]的和,状态转移方程为:
f
(
i
,
j
)
=
m
i
n
(
f
(
i
−
1
,
j
)
,
f
(
i
,
j
−
1
)
)
+
g
r
i
d
[
i
]
[
j
]
f(i,j)=min(f(i-1,j), f(i,j-1))+grid[i][j]
f(i,j)=min(f(i−1,j),f(i,j−1))+grid[i][j]
符合最优子结构,所以可以使用动态规划,代码如下:
class Solution {
public:
int minPathSum(vector<vector<int>>& grid)
{
int rows = grid.size();
int cols = grid[0].size();
vector<vector<int>> dp(rows, vector<int>(cols));
//dp表示到达这个格子的最小路径
int sum1 = 0;
for (int j = 0; j < cols; ++j)
{
//第一行的格子都只能是往右走过来的 不可能是其他路径过来的
sum1 += grid[0][j];
dp[0][j] = sum1;
}
int sum2 = 0;
for (int i = 0; i < rows; ++i)
{
//同理 第一列的格子只能是从左上角往下走才能到达
//不可能是其他路径过来的
sum2 += grid[i][0];
dp[i][0] = sum2;
}
for (int i = 1; i < rows; ++i)
{
for (int j = 1; j < cols; ++j)
{
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][cols - 1];
}
};
时间复杂度:
O
(
m
n
)
O(mn)
O(mn)
空间复杂度:
O
(
m
n
)
O(mn)
O(mn)
注意到,如果先遍历行再遍历列,本题的状态转移方程仅和上一轮外层循环,即前一行的状态,以及遍历列时上一次的计算结果有关;
同理,如果先遍历列再遍历行,本题的状态转义方程仅和上一轮的外层循环,即前一列的状态,以及遍历行是上一次的计算结果有关;
所以我们可以先比较行数和列数的大小,如果行数小,就在内层循环遍历行,开辟一个数组
d
p
dp
dp来储存上一轮外层遍历,即在上一列时的行遍历结果;如果列数小,就在内层循环遍历列,开闭一个数组
d
p
dp
dp来储存上一轮的外层遍历结果,即在上一行时的列遍历结果。这样空间复杂度就可以优化到
O
(
m
i
n
(
m
,
n
)
)
O(min(m,n))
O(min(m,n))
注意控制边界条件,如果是行数等于0,那么此时的这一行上每一列对应的最短路径仅和前一列的值以及
g
r
i
d
[
i
]
[
j
]
grid[i][j]
grid[i][j]有关;如果列数等于0,那么此时这一列上每一行的对应对端路径仅和前一行的值以及
g
r
i
d
[
i
]
[
j
]
grid[i][j]
grid[i][j]有关,需要注意的是储存行状态时,
d
p
[
i
−
1
]
dp[i - 1]
dp[i−1]等价与
f
(
i
−
1
,
j
)
f(i-1,j)
f(i−1,j),
d
p
[
i
]
dp[i]
dp[i]等价于
f
(
i
,
j
−
1
)
f(i,j-1)
f(i,j−1);储存列状态时,
d
p
[
j
]
dp[j]
dp[j]等价于
f
(
i
−
1
,
j
)
f(i-1,j)
f(i−1,j),
d
p
[
j
−
1
]
dp[j-1]
dp[j−1]等价于
f
(
i
,
j
−
1
)
f(i,j-1)
f(i,j−1),注意据此控制边界条件。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid)
{
int rows = grid.size();
int cols = grid[0].size();
int ret = 0;
if (rows > cols)
{
vector<int> dp(cols);
//如果cols小 就把它放在内层循环
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
//dp[j]相当于f(i - 1, j)
//dp[j - 1]相当于f(i, j - 1)
if (i == 0 && j == 0)
{
//首个元素
dp[j] = grid[i][j];
}
else if (i == 0)
{
//如果i==0 表明在第一行
//只能是和前继求和 即dp[j - 1]
dp[j] = dp[j - 1] + grid[i][j];
}
else if (j == 0)
{
//如果j==0 表明是在第1列
//只和上一层的有关
//所以和dp[j]求和
dp[j] = dp[j] + grid[i][j];
}
else
{
//否则 f(i-1, j)和f(i,j-1)中取小
dp[j] = min(dp[j], dp[j - 1]) + grid[i][j];
}
}
}
ret = dp[cols - 1];
}
else
{
vector<int> dp(rows);
//如果rows小 就把rows放在内层循环
for (int j = 0; j < cols; ++j)
{
for (int i = 0; i < rows; ++i)
{
//dp[i]相当于f(i, j - 1)
//dp[i - 1]相当于f(i - 1, j)
if (i == 0 && j == 0)
{
//起始位置
dp[i] = grid[i][j];
}
else if (j == 0)
{
//如果在第1列
//只和第1列前一行的数有关 即f(i - 1, j)
//即dp[i - 1]
dp[i] = dp[i - 1] + grid[i][j];
}
else if (i == 0)
{
//如果在第1行
//只和第一行前一列的数有关 即f(i,j - 1)
//即dp[i]
dp[i] = dp[i] + grid[i][j];
}
else
{
//否则 f(i - 1,j) f(i, j - 1)取小
dp[i] = min(dp[i - 1], dp[i]) + grid[i][j];
}
}
}
ret = dp[rows - 1];
}
return ret;
}
};
动态规划求最小路径和
本文介绍了一种利用动态规划解决寻找矩阵中从左上角到右下角的最小路径和的方法。通过定义状态f(i,j)并设置边界条件,实现了高效求解。文章提供了两种实现方式,并详细解释了如何优化空间复杂度。
1634

被折叠的 条评论
为什么被折叠?



