题目描述:
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], 而不像从顶到底那样需要最后还需要做一次循环。
通过本题,我意识到,即使是动态规划,也可以考虑多种策略,例如本题的从顶到底或从底到顶两种策略,不同动态规划的策略时间和空间效率可能会差很多。
