演算法學習->求解三角形最小路徑及其值

 

00 問題

00-1 描述

對給定高度為n的一個整數三角形,找出從頂部到底部的最小路徑和。每個整數只能向下移動到與之相鄰的整數。

找到一個一樣的力扣題:120. 三角形最小路徑和 – 力扣(LeetCode) (leetcode-cn.com)

示例1:
輸入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
輸出:11
解釋:如下面簡圖所示:
  2
 3 4
6 5 7
4 1 8 3
自頂向下的最小路徑和為 11(即,2 + 3 + 5 + 1 = 11)。
   
示例2:
輸入:triangle = [[-10]]
輸出:-10

00-2 提示:

1 <= triangle.length <= 200
triangle[0].length == 1
triangle[i].length == triangle[i - 1].length + 1
-104 <= triangle[i][j] <= 104

01 思路

想用動態規劃寫出來,重點在於狀態轉移方程。

將等腰三角形抽象為等腰直角三角形,如下

  0 1 2 3
0 2
1 3 4
2 6 5 7
3 8 3 9 2

加上下標化的序列,我們就可以用二維數組dp來考慮。dp是用來存儲到i,j位置後用到的最短路徑長度,比如dp[2] [2]=2+4+7=13

定義一個起點:

dp[0][0] = a[0][0];

三種情況:

  1. 三角形左路,在直角圖裡就是第一列,滿足:

    dp[i][0]=dp[i-1][0];
  2. 三角形右路,在直角圖裡是對角線,滿足:

    dp[i][i]=dp[i-1][i-1]+a[i][i]
  3. 普通位置

    dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+a[i][j];

這樣程式就很好寫了。就是往dp數組裡填數就行,最後篩出最後一行的最小值就行。

02 程式碼

class Solution {
public:
   int minimumTotal(vector<vector<int>>& triangle) {
       int len = triangle.size();
       int dp[200][200]={0};
       dp[0][0]=triangle[0][0];
       for(int i=1;i<len;i++){
           dp[i][0] = dp[i-1][0]+triangle[i][0];
      }
       for(int i=1;i<len;i++){
           dp[i][i] = triangle[i][i]+dp[i-1][i-1];
      }
       for(int i=2;i<len;i++){
           for(int j=1;j<i;j++){
               dp[i][j] = triangle[i][j]+min(dp[i-1][j], dp[i-1][j-1]);
          }
      }
       //填充dp
       //下面篩選路徑最短
       int ans = dp[len-1][0];
       for(int j = 1;j < len;j++){
           if(dp[len-1][j]<ans){
               ans = dp[len-1][j];
          }
      }
       return ans;
  }
};

03 升級版–記錄路徑

03-1 思路

如果要記下路徑,需要再來一個二維數組pre來記錄坐標為i,j的點的前一個節點。那麼如何記錄呢?我們看一下:

  • (i,i)的前一個節點就是(i-1,i-1);

  • (i,j)的前一個節點是(i-1,j)或者(i-1,j-1);

  • (i,0)的前一個節點是(i-1,0)。

容易從這些情況總結出,上一節點一定為i-1,只需記錄j的值即可。故我們在pre二維數組裡記錄的就是當前節點的前一節點的j值。

記錄之後,我們還需要輸出這個最小路徑。怎麼輸出呢?

我們在前一問題的基礎上得到最後行的最小值的列值後,將它返回主控函數,並用它作為參數給路徑輸出函數Disppath。

輸出方法為:

  • 對於當前節點,入棧,查它的pre數組值,更新,繼續該操作,直到完成。

    更新操作為:

     path.push_back(a[i][k]);
    k=pre[i][k];
  • 逐個出棧。

 

03-2 程式碼

//三角形最小路徑
#include<stdio.h>
#include<vector>
#include<string.h>
using namespace std;
#define MAXN 100
int a[MAXN][MAXN];//三角形
int n;//高度
int ans = 0;//應求的路徑長度
int dp[MAXN][MAXN];
int pre[MAXN][MAXN];//前驅結點
//根據狀態轉移方程,只記錄列號即可
int Search(){
   int i,j;
   dp[0][0] = a[0][0];
   for(i=1;i<n;i++){
       dp[i][0]=dp[i-1][0]+a[i][0];
       pre[i][0]=i-1;
  }
   for(i=1;i<n;i++){
       dp[i][i]=dp[i-1][i-1]+a[i][i];
       pre[i][i]=i-1;
  }
   for(i=2;i<n;i++){
       for(j=1;j<i;j++){
           if(dp[i-1][j-1]<dp[i-1][j]){
               dp[i][j]=dp[i-1][j-1]+a[i][j];
               pre[i][j]=j-1;
          }
           else{
               dp[i][j]=dp[i-1][j]+a[i][j];
               pre[i][j]=j;          
          }
      }
  }
   ans = dp[n-1][0];
   int k=0;
   for ( j = 1; j < n; j++)
  {
       if(ans>dp[n-1][j]){
           ans = dp[n-1][j];
           k=j;
      }
  }
   return k;
}
void Disppath(int k){
   int i=n-1;
   vector<int>path;//路徑節點
   while(i>=0){
       path.push_back(a[i][k]);
       k=pre[i][k];
       i--;
  }
   vector<int>::reverse_iterator it;
   for(it=path.rbegin();it!=path.rend();++it){
       printf("%d ",*it);
  }
   printf("\n");
}

int main(){
   int k;//k行k列的三角形
   memset(pre, 0, sizeof(pre));
   memset(dp, 0, sizeof(dp));
   scanf("%d",&n);
   for(int i=0;i<n;i++){
       for(int j=0;j<=i;j++){
           scanf("%d",&a[i][j]);
      }
  }
   k=Search();
   Disppath(k);
   printf("%d\n",ans);
   return 0;
}