LeetCode #120 Triangle - 刷題之旅
1 題目描述
給定一個三角形,找到從頂部到底部的最小路徑和。每一步只能移動到下一行的相鄰元素上。
2 解法
其實這題與leetcode #300 Longest Increasing Subsequence有點類似,如果我們嘗試用樹狀圖,把所有可能寫出來,如果今天題目是[[1], [2,3], [4,5,6], [7,8,9,10]]
大概會長以下這樣:
特徵如下:
- 從底部慢慢往上走,算出每層由下往上的最佳解
- dp 的值是由下往上的最佳解
2.1 Recursion
但我們先慢慢來吧,先寫出Recursion的寫法。他的問題也是經典的拿與不拿問題。題目可以看到,如果我這一層layer取i,那我下一層layer只能取i或是i+1,這兩個選最小的。
其實Recursive的關係式
(helper是recursive的function)
1 | min(helper(layer+1, i), helper(layer+1, i+1)) + triangle[layer][i] |
那最小問題基本上就是當已經走到底了,直接回傳triangle[layer][i]
,因為這時候就是最底層了,不用再往下走了。
1 | # index 從 0 開始記得 -1 |
完整程式碼
1 | def minimumTotal(self, triangle: List[List[int]]) -> int: |
2.2 Recursion + Memoization
接下來我們就是要加上Memoization,這樣就不用一直重複計算了。這裡我們可以用一個dp
的list來記錄已經計算過的值,我們可以先建立一個與triangle
一樣大小的list,然後每次計算過的值就存進去,下次再遇到就直接回傳。
建立一個dp[layer][idx]
是當前的從底部走上去的最佳解
1 | dp = [[0 for _ in range(len(triangle[i]))] for i in range(len(triangle))] |
當發現dp不是0時,直接回傳
1 | if dp[layer][idx] != 0: |
完整程式碼
1 | ~def minimumTotal2(self, triangle: List[List[int]]) -> int: |
2.3 Iterative + Tabulation
既然從底部走上去,我們可以從底部開始往上走,這樣就不用一直遞迴了。我們可以從倒數第二層開始,然後往上走,每一層都是取最小的值加上自己,這樣就可以得到最後的答案。
1 | def minimumTotal3(self, triangle: List[List[int]]) -> int: |
2.4 Iterative + Tabulation (Space Optimized)
其實我們可以發現,我們每次只會用到下一層的值,所以我們可以只用一個list來存下一層的值,這樣就可以省下空間。
1 | def minimumTotal4(self, triangle: List[List[int]]) -> int: |
其實還有更扯的,不需要建立dp
,直接用triangle
就可以了,因為我們每次都會更新triangle
的值,所以不用另外建立一個list。
雖然他會改動到原本的triangle
,但是到最後整個triangle
的值就是從底部走上去的最佳解。
1 | def minimumTotal5(self, dp: List[List[int]]) -> int: |
3 結論
這題我很有成就感,自己大概15分鐘就可以寫出Recursion + Memoization的解法,然後再花了10分鐘寫出Iterative + Tabulation的解法,這樣就可以了。這題其實很簡單,只要從底部往上走,每一層都取最小的值加上自己,這樣就可以得到最後的答案。
只要是take與不take的問題,我發現我已經慢慢可以習慣了。