题目中给出的三角形有一个树状的结构,这就可以让我们想到使用遍历之类的算法比如说DFS。但是如果我们忍者你分析一下的话,可以发现相邻的结点总是分享同一条边,也就是说,这道题目中存在重叠子问题(overlapping subproblems)。另外,假设结点x和结点y是k的孩子,只要从底层到结点x和y的最短路径长度都能够知道,那么从底层到结点k的最短路径长度可以在O(1)时间内判断出来,这就是最优子结构(optimal subtructure)。因此,就时间复杂度考虑,动态规划或许是一个不错的解法。
我们从底层向上看,也就是Bottom-up DP,最底层结点的minPath就是它们自身的值,而上一层节点的minPath是它下方左侧结点和下方右侧节点的最小值加上自身的值,根据这个规律,我们可以写出下面的递推式:
minPath[k][i] = min(minPath[k + 1][i], minPath[k + 1][i + 1]) + triangle[k][i];
到现在我们已经可以解决这道题目了,但是我们额外需要题目给出的triangle大小的额外空间用来存储中间结果,当triangle很大的时候,这个算法可能会消耗大量的内存,那么这个算法可否进行优化呢?答案是可以的,进一步地观察我们可以发现,当第k层的所有结点的minPath都已经计算出来之后,第k + 1层所有阶段的minPath都已经不再会用到了,所以我们可以使用一个数组,通过不断地赋给它每一层结点的minPath实现递推和迭代,也就是下面的公式:
minPath[k] = min(minPath[k] + minPath[k + 1]) + triangle[k][i](这里等号右侧的minPath[k]和minPath[k + 1]是上一层的minPath,等号左侧为这一层的minPath);
注意在最底层的时候,元素的数量为n,根据上面的递推式,我们需要长度为n + 1的数组。这个算法的时间复杂度为O(n*2),空间复杂度为O(n),代码如下:
public class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
if(triangle == null || triangle.size() == 0 || triangle.get(0).size() == 0) return 0; // corner case
int[] minLength = new int[triangle.size() + 1];
for(int layer = triangle.size() - 1; layer >= 0; layer--){
for(int i = 0; i < triangle.get(layer).size(); i++){
minLength[i] = triangle.get(layer).get(i) + Math.min(minLength[i], minLength[i + 1]);
}
}
return minLength[0];
}
}
知识点:
1. 动态规划问题的分析思路
2. 动态规划的优化方式,首先写出非优化版本的算法,然后在使用空间的问题上通过分析问题发现是否可以迭代,如果可以迭代则进行优化