leetcode120. Triangle

18 篇文章 0 订阅

 

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

Note:

Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.


 

This problem is quite well-formed in my opinion. The triangle has a tree-like structure, which would lead people to think about traversal algorithms such as DFS. However, if you look closely, you would notice that the adjacent nodes always share a 'branch'. In other word, there are overlapping subproblems. Also, suppose x and y are 'children' of k. Once minimum paths from x and y to the bottom are known, the minimum path starting from k can be decided in O(1), that is optimal substructure. Therefore, dynamic programming would be the best solution to this problem in terms of time complexity.

 

What I like about this problem even more is that the difference between 'top-down' and 'bottom-up' DP can be 'literally' pictured in the input triangle. For 'top-down' DP, starting from the node on the very top, we recursively find the minimum path sum of each node. When a path sum is calculated, we store it in an array (memoization); the next time we need to calculate the path sum of the same node, just retrieve it from the array. However, you will need a cache that is at least the same size as the input triangle itself to store the pathsum, which takes O(N^2) space. With some clever thinking, it might be possible to release some of the memory that will never be used after a particular point, but the order of the nodes being processed is not straightforwardly seen in a recursive solution, so deciding which part of the cache to discard can be a hard job.

 

'Bottom-up' DP, on the other hand, is very straightforward: we start from the nodes on the bottom row; the min pathsums for these nodes are the values of the nodes themselves. From there, the min pathsum at the ith node on the kth row would be the lesser of the pathsums of its two children plus the value of itself, i.e.:

 

minpath[k][i] = min( minpath[k+1][i], minpath[k+1][i+1]) + triangle[k][i];

 

Or even better, since the row minpath[k+1] would be useless after minpath[k] is computed, we can simply set minpath as a 1D array, and iteratively update itself:

解释:一行数据,每次用12中最小的来更新第一个位置,之后再用23来更新呢2,也就是每次都用两个来更新前一个,这样就可以完成一维数组的更新

For the kth level:
minpath[i] = min( minpath[i], minpath[i+1]) + triangle[k][i]; 

 

Thus, we have the following solution

int minimumTotal(vector<vector<int> > &triangle) {
    int n = triangle.size();
    vector<int> minlen(triangle.back());
    for (int layer = n-2; layer >= 0; layer--) // For each layer
    {
        for (int i = 0; i <= layer; i++) // Check its every 'node'
        {
            // Find the lesser of its two children, and sum the current value in the triangle with it.
            minlen[i] = min(minlen[i], minlen[i+1]) + triangle[layer][i]; 
        }
    }
    return minlen[0];
}

【分析】

    首先重述一下题意:输入一个(等腰)三角形数阵,寻找从三角形顶部达到底部的最小路径和,从顶部达到底部的过程中,从上一行的某个位置只能移动到下一行的与其相邻位置,如题中举例:第一行的2可以移动到第二行的3或者4;第二行的3只能移动到第三行的6或者5,第二行的4只能移动到第三行中的5或者7,以此类推。

     此题明显是一个求取约束条件下最小路径的题,用动态规划(Dynamic Programming)解再合适不过,既然是DP问题,那么我们需要抽象出状态转移方程:把三角形数阵可以抽象成一个二维矩阵,那么:

     设:从位置(i,j)达到底部的最小路径和为MP(i,j);根据约束条件,从位置(i,j)只能达到下一行的(i+1,j)和(i+1,j+1)两个位置;如果,根据题意我们知道,位置(i,j)处的权值为输入三角形数阵对应的数据:triangle[i][j];So,状态转移方程为:MP(i,j)=min{MP(i+1,j),MP(i+1,j+1)}+triangle[i][j];三角形顶部到底部最小路径和:MP(0,0)=min{MP(1,0),MP(1,1)}+triangle[0][0];而:MP(1,0)=min{MP(2,0),MP(2,1)}+triangle[1][0];MP(1,1)=min{MP(2,1),MP(2,2)}+triangle[1][1]....

    很明显,这种自顶向下的求解方式会形成一个“树形结构”,并且自顶向下的求解过程,计算式中一直存在未知式,这显然不是一种好的方式,因此,我们采用自底向上的求解思路:以题目中给出的例子为例:

MP(3,0)=triangle[3][0]=4;

MP(3,1)=triangle[3][1]=1;

MP(3,2)=triangle[3][1]=8;

MP(3,3)=triangle[3][1]=3;

MP(2,0)=min{MP(3,0),MP(3,1)}+triangle[2][0]=1+6=7;

MP(2,1)=min{MP(3,1),MP(3,2)}+triangle[2][1]=1+5=6;

MP(2,2)=min{MP(3,2),MP(3,3)}+triangle[2][2]=3+7=10;

MP(1,0)=min{MP(2,0),MP(2,1)}+triangle[1][0]=6+3=9;

MP(1,1)=min{MP(2,1),MP(2,2)}+triangle[1][1]=6+6=12;

MP(0,0)=min{MP(1,0),MP(1,1)}+triangle[0][0]=9+2=11;

很明显,自底向上计算,最后MP(0,0)就是我们要的结果,采用两重循环即可完成求解,程序如下:

 

      但是,像这样解决,需要消耗额外的空间,空间复杂度为O(n2),因此该解法不可取,我们可以对其进行改进,借助输入数阵本身的空间来存储中间的迭代值:

 

完整代码为:

           分析:这样做虽然表面上节省了空间消耗,但是事实上依赖于输入数阵的空间,空间消耗并没有实际减小,并且会改变输入三角形数阵的原始数据,很多时候,这样做都是不可取的。

 

【解法一:基于C++解法】

    经过上面的分析,我们进一步优化空间消耗的问题,事实上,我们可以用一个一维数组来存储自底向上的路径向量,路径向量初始化为三角形数阵底部向量,此后每计算一次,更新一次,空间复杂度为O(n),且不影响输入三角形数阵的原始数据,程序如下:

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值