題目敘述

- 題目難度:
Medium
- 題目敘述: 題目給定一個2D陣列
triangle,求從最頂端走到最底端的最小路徑總和值
對於每個 row 只能往下一層走,並且每次都會有兩種走法,假設現在在當前row的位置是 i,則下一層能夠選擇繼續走到 i 或者是 i+1 的位置。
1 2 3 4 5 6
| 範例: 2 3 4 6 5 7 4 1 8 3 最短路徑總和會是: 2 + 3 + 5 + 1 = 11
|
解法
一開始的想法
想法其實也還蠻單純的,就是每層都可以有選或不選特定路徑,由於要找最短的路徑和, 因此需要兩條路徑都選擇,比較回傳結果大小 ,然後選小的回傳,這樣整體遞迴呼叫完畢後就能夠求出最短路徑和。
我的解法
Recursive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { public: int helper(vector<vector<int>>& triangle, int row, int index ,int result ){ if(row == triangle.size()){ return result; } int r1 = helper(triangle, row+1, index, result+triangle[row][index]); if(index+1 < triangle[row].size()){ int r2 = helper(triangle, row+1, index+1, result+triangle[row][index+1]); return min(r1,r2); } else return r1; }
int minimumTotal(vector<vector<int>>& triangle){ int result = 0; result += triangle[0][0]; return helper(triangle, 1, 0, result); }
};
|
這是初次找出遞迴關係後的版本,首先在 minimumTotal中會初始化一個用於儲存路徑和的變數 result,由於頂端元素必定存在 (題目的限制),因此可以直接先加入到 result,接著呼叫 helper(),在 helper 函數中, r1 和 r2 分別儲存 helper() 函數遞迴呼叫的結果,接著比較 min(r1,r2) 回傳路徑和比較小的結果。 而遞迴呼叫的停止條件就是當 row 到達最後一層。
但這樣包含了大量的重複計算,會花費大量時間,因此會是 Time Limit Exceeded
Recursive + Memoization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public: vector<vector<int>> dp; int helper(vector<vector<int>>& triangle, int row, int index ){ if(row == triangle.size()){ return 0; } if(dp[row][index]!=INT_MAX) return dp[row][index]; int r1 = helper(triangle, row+1, index); int r2 = helper(triangle, row+1, index+1); dp[row][index] = triangle[row][index] + min(r1, r2); return dp[row][index]; }
int minimumTotal(vector<vector<int>>& triangle){
dp = vector<vector<int>>(triangle.size(), vector<int>(triangle.size(), INT_MAX)); return helper(triangle, 0, 0); }
};
|
這裡透過另外一個二維向量 dp 來儲存重複運算的結果,這裡將向量初始化為 INT_MAX,而在 helper 函數中,一旦發現有先前計算過的結果就直接回傳,並且由於已經有用於儲存計算結果的 dp,因此也不需要額外的變數來儲存最短路徑和了
執行結果

但這應該也算是複雜度較高的做法了
其他做法
1 2 3 4 5 6 7 8 9 10 11
| class Solution { public: int minimumTotal(vector<vector<int> > &triangle) { vector<int> mini = triangle[triangle.size()-1]; for ( int i = triangle.size() - 2; i>= 0 ; --i ) for ( int j = 0; j < triangle[i].size() ; ++ j ) mini[j] = triangle[i][j] + min(mini[j],mini[j+1]); return mini[0]; } };
|
執行結果

複雜度
| 方法類型 |
時間複雜度 |
簡略時間複雜度分析 |
| Recursive + Memoization |
$O(n^2)$ |
每個節點計算一次,三角形總節點數為 $n(n+1)/2$ |
| Iteration(Bottom Up) |
$O(n^2)$ |
遍歷所有節點,每層需要處理該層節點數量,總計 $n(n+1)/2$ |
| 方法類型 |
空間複雜度 |
簡略空間複雜度分析 |
| Recursive + Memoization |
$O(n^2)$ |
使用大小為 $n \times n$ 的記憶化表 + 遞迴棧深度為 $O(n)$ |
| Iteration(Bottom Up) |
$O(n)$ |
僅用一個大小為 $n$ 的一維陣列保存當前層與下一層結果 |