【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)

参考

1、https://leetcode-cn.com/problems/triangle/solution/javadong-tai-gui-hua-si-lu-yi-ji-dai-ma-shi-xian-b/

原文地址:https://www.cnblogs.com/flix/p/12747902.html