1. 程式人生 > >【LeetCode】120. Triangle 基於C++和Java的分析及解法,動態規劃

【LeetCode】120. Triangle 基於C++和Java的分析及解法,動態規劃

120. Triangle

Total Accepted: 69567Total Submissions: 229977Difficulty: Medium

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.

【分析】

    首先重述一下題意:輸入一個(等腰)三角形數陣,尋找從三角形頂部達到底部的最小路徑和,從頂部達到底部的過程中,從上一行的某個位置只能移動到下一行的與其相鄰位置,如題中舉例:第一行的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)就是我們要的結果,採用兩重迴圈即可完成求解,程式如下:

int minimumTotal(vector<vector<int>>& triangle) 
    {
        int length=triangle.size();
        if(length==0)return 0;
        if(length==1)return triangle[0][0];
        vector<int>temp(triangle.size(),0);   
        vector<vector<int>> MP(length,temp);
        MP[length-1]=triangle[length-1];//三角形底部一行任何一個位置達到底部最短路徑為其本身
        
        for(int i=length-2;i>=0;i--)
        {
            for(int j=0;j<triangle[i].size();j++)
            {
                MP[i][j]=min(MP[i+1][j],MP[i+1][j+1])+triangle[i][j];
            }
        }
        return MP[0][0];
    }
   但是,像這樣解決,需要消耗額外的空間,空間複雜度為O(n2),因此該解法不可取,我們可以對其進行改進,藉助輸入數陣本身的空間來儲存中間的迭代值: 完整程式碼為:
int minimumTotal(vector<vector<int>>& triangle) 
    {
        int length=triangle.size();
        if(length==0)return 0;
        if(length==1)return triangle[0][0];
        for(int i=triangle.size()-2;i>=0;i--)
        {
            for(int j=0;j<triangle[i].size();j++)
            {
                triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
            }
        }
        return triangle[0][0];
    }
分析:這樣做雖然表面上節省了空間消耗,但是事實上依賴於輸入數陣的空間,空間消耗並沒有實際減小,並且會改變輸入三角形數陣的原始資料,很多時候,這樣做都是不可取的。

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

    經過上面的分析,我們進一步優化空間消耗的問題,事實上,我們可以用一個一維陣列來儲存自底向上的路徑向量,路徑向量初始化為三角形數陣底部向量,此後每計算一次,更新一次,空間複雜度為O(n),且不影響輸入三角形數陣的原始資料,程式如下:

int minimumTotal(vector<vector<int>>& triangle) 
    {
        int length=triangle.size();
        if(length==0)return 0;//特殊情況處理,容錯機制
        if(length==1)return triangle[0][0];  
        vector<int> sum=triangle[triangle.size()-1];//初始化路徑和儲存向量,自底向上
        
        for(int i=triangle.size()-2;i>=0;i--)//自底向上迭代
        {
            for(int j=0;j<triangle[i].size();j++)
            {
                sum[j]=min(triangle[i][j]+sum[j],triangle[i][j]+sum[j+1]);
            }
        }
        return sum[0];
    }

執行結果:


【解法二:基於Java的解法】

     根據上面的思路,Java的解法程式如下:

public int minimumTotal(List<List<Integer>> triangle)
    {
        int length=triangle.size();
        if(length==0)return 0;
        if(length==1)return triangle.get(0).get(0);
        
        List<Integer> sum=triangle.get(length-1);
        for(int i=length-2;i>=0;i--)
        {
            for(int j=0;j<triangle.get(i).size();j++)
            {
                int sum1=triangle.get(i).get(j)+sum.get(j);
                int sum2=triangle.get(i).get(j)+sum.get(j+1);
                sum.set(j,Math.min(sum1,sum2));
            }
        }
        return sum.get(0);
        
    }