演算法課第11周第1題——120. Triangle
題目描述:
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).
程式程式碼:
class Solution { public: int minimumTotal(vector<vector<int>>& triangle) { // 計算三角形層數 int n = triangle.size(); // 從底端開始往上計算,避免考慮兩邊的多餘處理,並簡化計算 // f[j]表示第j列處的最小和。因為從下往上計算,因此不需要二維陣列,節約了空間(類似用一維陣列做揹包問題) // f陣列初始賦值取三角形最下層 vector<int> f(triangle[n - 1]); // 往上計算,取該處triangle值加上下一層的左下或右下的值 for (int i = n - 2; i >= 0; i--) { for (int j = 0; j <= triangle[i].size(); j++) { f[j] = min(f[j] + triangle[i][j], f[j + 1] + triangle[i][j]); } } // 往上計算到最後,只需要返回f[0]的值 return f[0]; } };
簡要題解:
本題使用了動態規劃演算法。
先理清題意。本題所給的輸入是一個由數字構成的三角形(表示為一個二維陣列)。需要求出該三角形中連線頂端和底端的路徑中最大的數字總和。
思考該題時,我一開始是考慮從頂端開始計算,可以參考下圖:
這樣的話,可以通過使用一個二層迴圈列舉i = 0 ~ n-1, j = 0~triangle[i].size, 得到轉移方程:
g[i][j] = min {g[i-1][j-1] + triangle[i][j], g[i-1][j] + triangle[i][j] }. 而最終的輸出則是min{ g[n-1][j], j從0到triangle[n-1].size()}
但是,用這個從頂端到底端的計算方法,有明顯的缺陷。首先,轉移方程並不完全正確,因為對於某一行兩側的數字(如上圖中第三行的4和6),就需要做分類的討論(因為某行最左側的數字的左上方沒有數字,最右側數字的右上方沒有數字),某行最左端處只能取g[i][j] = g[i-1][j] + triangle[i][j], 而最右端只能取g[i][j] = g[i-1][j] + triangle[i][j], 這樣分類討論起來就麻煩不少; 第二,這樣計算時最後一步計算最終輸出還需要對n-1行做一個迴圈,若該行中數字很多,可能會大大增加計算時間;第三,這樣運算需要再用到一個二維陣列g[i][j],對空間的利用也相對沒太高的效率。
為了解決這些問題,我經過思考,聯想到揹包問題中用一維陣列代替二維陣列的方法,考慮改變策略,從底端到頂端來計算。這樣,就不需要一個二維陣列,而只需要一個一維陣列f[j],其初始化為三角形最底端的一行(即triangle[n-1]). 通過列舉i = n-2 ~ 0, j = 0 ~ triangle[i].size(), 可以列出轉移方程:
f[j] = min(f[j] + triangle[i][j], f[j + 1] + triangle[i][j])
這樣從底端到頂端計算的方法,同時還解決了需要分類討論某行兩側數字的問題,而只要統一考慮下方一層的左下和右下兩個數字。此外,這樣計算,最終的輸出就是f[0], 而不像從頂到底那樣需要最後還需要做一次迴圈。
通過本題,我意識到,即使是動態規劃,也可以考慮多種策略,例如本題的從頂到底或從底到頂兩種策略,不同動態規劃的策略時間和空間效率可能會差很多。