LeetCode - 120. Triangle

題目中給出的三角形有一個樹狀的結構,這就可以讓我們想到使用遍歷之類的算法比如說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. 動態規劃的優化方式,首先寫出非優化版本的算法,然後在使用空間的問題上通過分析問題發現是否可以迭代,如果可以迭代則進行優化


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章