1. 程式人生 > >演算法課第11周第1題——120. Triangle

演算法課第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], 而不像從頂到底那樣需要最後還需要做一次迴圈。

通過本題,我意識到,即使是動態規劃,也可以考慮多種策略,例如本題的從頂到底或從底到頂兩種策略,不同動態規劃的策略時間和空間效率可能會差很多。