120. 三角形最小路径和

题目

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
在这里插入图片描述
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/triangle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码及分析

这道题目很容易想成用贪婪法进行求解。题目中所给的测试案例确实可以采用贪婪法得到,但再看看下面这种情况。
在这里插入图片描述
上述矩阵,如果采用贪婪法,则其最小路径为2 -> 3 -> 5 -> 4 ,最小路径和为14,
实际上,其最小路径应为 2 -> 3 -> 6 -> 1, 最小路径和为12。
由这个例子可以看出,贪婪法是行不通的,这是因为如果采用贪婪法进行求解,容易陷入局部最小值当中,而非全局最小值,可等对于整个矩阵的前部分层而言,得到了最小值,但继续向下走时,已经不再考虑的那些数值可能要远远小于当前所考虑局部区域的所有值。

方法一:动态规划法(空间复杂度O(1))

这道题可以用动态规划的思想去解决。我们不能保证那条路径上其和为最小值,那么我们就采用动态规划的方法计算所有可能路径的较小值。
在这里我们可以另外建立动态转移矩阵,而是直接用保存矩阵值的triangle来作为动态转移矩阵。当其作为动态转移矩阵存在时,其中的没一个元素triangle[i][j]表示的是从矩阵顶端到达该位置的最短路径之和,那么对于该矩阵的最后一行而言,我们将计算出从顶端到达底端所有位置的最小值,其中这些最小值中的最小值,便是我们所需求解的最小路径和。
再来看一下动态转移矩阵triangle的具体更新过程:
对于triangle[i][j],其职能从上方与它相邻的位置移动过来,即triangle[i-1][j]和triangle[i-1][j-1],那么到达给点的最短路径必定是从上一行中相邻两节点的较小值处移动过来,则有:
triangle[i][j] += min(triangle[i-1][j-1],triangle[i-1][j]);
需要注意的是,有两个边界问题需要考虑到,
(1)对于每一行的第一个节点,上一行与之相邻的节点只有triangle[i-1][0];
(2)对于每一行的最后一个节点,上一行与之相邻的节点只有triangle[i-1][j-1]。

 int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.empty())
            return 0;
        int rows = triangle.size();
        for(int i = 1; i < rows; i++)
        {
            for(int j = 0; j < triangle[i].size(); j++)
            {
                if(j == 0)
                    triangle[i][0] += triangle[i-1][0];
                else if(j == triangle[i].size()-1)
                    triangle[i][j] += triangle[i-1][j-1];
                else
                    triangle[i][j] += min(triangle[i-1][j-1],triangle[i-1][j]);
            }
        }
        sort(triangle[rows-1].begin(),triangle[rows-1].end());
        return triangle[rows-1][0];        
    }
复杂度:

时间复杂度: O(n^2) ( ∑ i = 2 n ( ( n + 2 ) ( n − 1 ) 2 ) \sum\limits_{i=2}^n(\frac{(n+2)(n-1)}{2}) i=2n2n+2(n1) )。
空间复杂度: O(1) 。

方法二:动态规划法(空间复杂度O(n^2))

上述方法一中,我们直接修改了给定的矩阵triangle,这样实际上在某些情况之下是不好的,我们也可以建立一个同等大小的矩阵dp用于动态转移。

 int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.empty())
            return 0;
        int rows = triangle.size();
        vector<vector<int>> dp(rows,vector<int>(rows,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][0] = triangle[i][0] + dp[i-1][0];
                else if(j == triangle[i].size()-1)
                    dp[i][j] = triangle[i][j] + dp[i-1][j-1];
                else
                    dp[i][j] += min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j];
            }
        }
        sort(dp[rows-1].begin(),dp[rows-1].end());
        return dp[rows-1][0];        
    }
复杂度:

时间复杂度: O(n^2) ;
空间复杂度: O(n^2) 。

方法三:动态规划法(空间复杂度O(n))

方法二中,我们的动态转移矩阵dp只与上一层中的dp值有关,故我们可以只建立一维的dp矩阵进行动态转移。
若我们依旧采用自顶向下的顺序,则有:

int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.empty())
            return 0;
        int rows = triangle.size();
        vector<int> dp(rows,0);
        dp[0] = triangle[0][0];
        for(int i = 1; i < rows; i++)
        {
            for(int j = 0; j <= i; j++)
            {
                if(j == 0)
                    dp[0] = triangle[i][0] + dp[0];
                else if(j == triangle[i].size()-1)
                    dp[j] = triangle[i][j] + dp[j-1];
                else
                    dp[j] = min(dp[j-1],dp[j])+triangle[i][j];
                cout << dp[j] <<" ";
            }
            cout << endl;
        }
        sort(dp.begin(),dp.end());
        return dp[0];        
    }

对于题目中的测试案例,
在这里插入图片描述
其计算过程中dp矩阵的更新过程为:
5 9
11 14 21
15 15 23 26
可以看到第一次更新dp时,其第二个元素便是错误的,原因是因为,我们在更新dp[0]时,已经修改了dp[0]的值,那么在计算dp[1]时,将采用新的dp[0]去更新。
故为了只采用O(n)的空间,我们应该用自底向上的方法去求解。

int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.empty())
            return 0;
        int rows = triangle.size();
        vector<int> dp(triangle[rows-1]);
        for(int i = rows-2; i >= 0; i--)
        {
            for(int j = 0; j <= i; j++)
            {
                dp[j] = min(dp[j+1],dp[j])+triangle[i][j];
            }
        }
        return dp[0];        
    }
复杂度:

时间复杂度: O(n^2) ;
空间复杂度: O(n) 。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值