【LeetCode-动态规划】三角形最小路径和
题目描述
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
示例:
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
题目链接: https://leetcode-cn.com/problems/triangle/
思路1
自顶向下动态规划。将三角形画成如下形式更好分析:
[2],
[3,4],
[6,5,7],
[4,1,8,3]
用rows表示三角形的行数,cols表示三角形的最大列数,dp[i][j]表示从第一行的顶点走到坐标为(i,j)位置的最短路径(rows,cols,i,j均从0开始计数)。分析过程如下
- 当i=0时,dp[0][0]=triangle[0][0],这是边界值;
- 当i=1时,因为每一步只能移动到下一行中相邻的结点上,所以当前位置的值取决于上一行的位置以及上一行左边一个位置,也就是dp[1][j]=min(dp[0][j-1], dp[0][j])+triangle[i][j],存在两种特殊情况:
- 当j=0时,该位置在行的最左边(例如第二行的3),此时dp[i][j]=dp[i-1][j]+triangle[i][j];
- 当j=rows时,该位置在行的最右边(例如第二行的4),此时dp[i][j]=dp[i-1][j-1]+triangle[i][j];
- 综上,dp[i][j]=min(dp[i-1][j], dp[i-1][j-1])+triangle[i][j],注意两种特殊情况即可。
代码如下:
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.empty()) return 0;
int rows = triangle.size();
int cols = triangle[triangle.size()-1].size();
vector<vector<int>> dp(rows, vector<int>(cols, 0));
dp[0][0] = triangle[0][0];
for(int i=1; i<rows; i++){
for(int j=0; j<=i; j++){
if(j==0){
dp[i][j] = dp[i-1][j] + triangle[i][j];
}else if(j==i){
dp[i][j] = dp[i-1][j-1] + triangle[i][j];
}else{
dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + triangle[i][j];
}
}
}
int minLen = 0x7fffffff;
for(int i=0; i<dp[rows-1].size(); i++){
minLen = min(dp[rows-1][i], minLen);
}
return minLen;
}
};
- 时间复杂度:O(n^2)
- 空间复杂度:O(n^2)
改写
- 状态定义:dp[i][j] 表示从顶点到位置 (i, j) 的最小路径和;
- 状态转移方程:dp[i][j] = min(dp[i-1][j], dp[i-1][j-1])+triangle[i][j] = min(dp[i-1][j]+triangle[i][j], dp[i-1][j-1]+triangle[i][j]);
- 边界条件:dp[i][j] 跟 dp[i-1][j] 和 dp[i-1][j-1] 有关, 因为形状是一个三角形,所以我们只有在
i-1>=0 && j<triangle[i-1].size()
时,dp[i-1][j]才有效;同理,只有在i-1>=0 && j-1<triangle[i-1].size()
时,dp[i-1][j-1] 才有效。
代码如下:
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.empty()) return 0;
if(triangle[0].empty()) return 0;
int rows = triangle.size();
int cols = triangle[rows-1].size();
vector<vector<int>> dp(rows, vector<int>(cols, 0));
for(int i=0; i<rows; i++){
for(int j=0; j<triangle[i].size(); j++){
int a = INT_MAX, b = INT_MAX;
if(i-1>=0 && j<triangle[i-1].size()) a = dp[i-1][j] + triangle[i][j];
if(j-1>=0 && j-1<triangle[i-1].size()) b = dp[i-1][j-1] + triangle[i][j];
dp[i][j] = min(a, b);
if(dp[i][j]==INT_MAX) dp[i][j] = triangle[i][j]; // dp[i-1][j], dp[i-1][j-1]均不在范围,对应顶点
}
}
int ans = INT_MAX;
for(int i=0; i<cols; i++) ans = min(dp[rows-1][i], ans);
return ans;
}
};
空间复杂度优化:
在计算第i行的最短路径时,只需要第i-1行的dp[i-1][j]和dp[i-1][j-1],可以使用两个变量pre和preLeft替代。使用dp[i]表示到第i行的最短路径,代码如下:
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.empty()) return 0;
int rows = triangle.size();
int cols = triangle[triangle.size()-1].size();
vector<int> dp(cols, 0);
dp[0] = triangle[0][0];
int pre = 0, preLeft = 0; // pre指dp[i-1][j], preLeft指dp[i-1][j-1]
for(int i=1; i<rows; i++){
for(int j=0; j<=i; j++){
pre = dp[j];
if(j==0){
dp[j] = pre + triangle[i][j];
}else if(j==i){
dp[j] = preLeft + triangle[i][j];
}else{
dp[j] = min(pre, preLeft) + triangle[i][j];
}
preLeft = pre;
}
}
int minLen = 0x7fffffff;
for(int i=0; i<dp.size(); i++){
minLen = min(dp[i], minLen);
}
return minLen;
}
};
- 时间复杂度:O(n^2)
- 空间复杂度:O(n)
思路2
自底向上动态规划。使用dp[i][j]表示包含第i行第j列元素的最短路径,因为dp[i][j]一定会到达dp[i+1][j]和dp[i+1][j+1],所以dp[i][j]=min(dp[i+1][j], dp[i+1][j+1])+triangle[i][j]。
代码如下:
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.empty()) return 0;
int rows = triangle.size();
int cols = triangle[triangle.size()-1].size();
vector<vector<int>> dp(rows+1, vector<int>(cols+1, 0));
for(int i=rows-1; i>=0; i--){
for(int j=0; j<triangle[i].size(); j++){
dp[i][j] = min(dp[i+1][j], dp[i+1][j+1])+triangle[i][j];
}
}
return dp[0][0];
}
};
- 时间复杂度:O(n^2)
- 空间复杂度:O(n^2)