在上一篇中,咱們經過題目「最長上升子序列」以及"最大子序和",學習了DP(動態規劃)在線性關係中的分析方法。這種分析方法,也在運籌學中被稱爲「線性動態規劃」,具體指的是 「目標函數爲特定變量的線性函數,約束是這些變量的線性不等式或等式,目的是求目標函數的最大值或最小值」。這點你們做爲了解便可,不須要死記,更不要生搬硬套!算法
在本節中,咱們將繼續分析一道略微區別於以前的題型,但願能夠由此題與以前的題目進行對比論證,進而順利求解!數組
第120題:給定一個三角形,找出自頂向下的最小路徑和。markdown
每一步只能移動到下一行中相鄰的結點上。ide
例如,給定三角形:函數
[學習
[2], [3,4],
[6,5,7],測試
[4,1,8,3]優化
]code
自頂向下的最小路徑和爲 11(即,2 + 3 + 5 + 1 = 11)。blog
本題有必定難度!
若是沒有思路請回顧上一篇的學習內容!
不建議直接看題解!
1156139D8_0.png
首先咱們分析題目,要找的是三角形最小路徑和,這是個啥意思呢?假設咱們有一個三角形:[[2], [3,4], [6,5,7], [4,1,8,3]]
那從上到下的最小路徑和就是2-3-5-1,等於11。
因爲咱們是使用數組來定義一個三角形,因此便於咱們分析,咱們將三角形稍微進行改動:
這樣至關於咱們將整個三角形進行了拉伸。這時候,咱們根據題目中給出的條件:每一步只能移動到下一行中相鄰的結點上。其實也就等同於,每一步咱們只能往下移動一格或者右下移動一格。將其轉化成代碼,假如2所在的元素位置爲[0,0],那咱們往下移動就只能移動到[1,0]或者[1,1]的位置上。假如5所在的位置爲[2,1],一樣也只能移動到[3,1]和[3,2]的位置上。以下圖所示:
題目明確了以後,如今咱們開始進行分析。題目很明顯是一個找最優解的問題,而且能夠從子問題的最優解進行構建。因此咱們經過動態規劃進行求解。首先,咱們定義狀態:
咱們很容易想到能夠自頂向下進行分析。而且,不管最後的路徑是哪一條,它必定要通過最頂上的元素,即[0,0]。因此咱們須要對dp[0][0]進行初始化。
繼續分析,若是咱們要求dp[i][j],那麼其必定會從本身頭頂上的兩個元素移動而來。
如5這個位置的最小路徑和,要麼是從2-3-5而來,要麼是從2-4-5而來。而後取兩條路徑和中較小的一個便可。進而咱們獲得狀態轉移方程:
可是,咱們這裏會遇到一個問題!除了最頂上的元素以外,
最左邊的元素只能從本身頭頂而來。(2-3-6-4)
最右邊的元素只能從本身左上角而來。(2-4-7-3)
而後,咱們觀察發現,位於第2行的元素,都是特殊元素(由於都只能從[0,0]的元素走過來)
咱們能夠直接將其特殊處理,獲得:
dp[1][0] = triangle[1][0] + triangle[0][0] dp[1][1] = triangle[1][1] + triangle[0][0]
最後,咱們只要找到最後一行元素中,路徑和最小的一個,就是咱們的答案。即:
l:dp數組長度
result = min(dp[l-1,0],dp[l-1,1],dp[l-1,2]....)
綜上咱們就分析完了,咱們總共進行了4步:
1.定義狀態
2.總結狀態轉移方程
3.分析狀態轉移方程不能知足的特殊狀況。
4.獲得最終解
分析完畢,代碼自成:
1func minimumTotal(triangle [][]int) int { 2 if len(triangle) < 1 { 3 return 0 4 } 5 if len(triangle) == 1 { 6 return triangle[0][0] 7 } 8 dp := make([][]int, len(triangle)) 9 for i, arr := range triangle { 10 dp[i] = make([]int, len(arr)) 11 } 12 result := 1<<31 - 1 13 dp[0][0] = triangle[0][0] 14 dp[1][1] = triangle[1][1] + triangle[0][0] 15 dp[1][0] = triangle[1][0] + triangle[0][0] 16 for i := 2; i < len(triangle); i++ { 17 for j := 0; j < len(triangle[i]); j++ { 18 if j == 0 { 19 dp[i][j] = dp[i-1][j] + triangle[i][j] 20 } else if j == (len(triangle[i]) - 1) { 21 dp[i][j] = dp[i-1][j-1] + triangle[i][j] 22 } else { 23 dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j] 24 } 25 } 26 } 27 for _,k := range dp[len(dp)-1] { 28 result = min(result, k) 29 } 30 return result 31} 32 33func min(a, b int) int { 34 if a > b { 35 return b 36 } 37 return a 38}
運行上面的代碼,咱們發現使用的內存過大。咱們有沒有什麼辦法能夠壓縮內存呢?經過觀察咱們發現,在咱們自頂向下的過程當中,其實咱們只須要使用到上一層中已經累積計算完畢的數據,而且不會再次訪問以前的元素數據。繪製成圖以下:
優化後的代碼以下:
1func minimumTotal(triangle [][]int) int { 2 l := len(triangle) 3 if l < 1 { 4 return 0 5 } 6 if l == 1 { 7 return triangle[0][0] 8 } 9 result := 1<<31 - 1 10 triangle[0][0] = triangle[0][0] 11 triangle[1][1] = triangle[1][1] + triangle[0][0] 12 triangle[1][0] = triangle[1][0] + triangle[0][0] 13 for i := 2; i < l; i++ { 14 for j := 0; j < len(triangle[i]); j++ { 15 if j == 0 { 16 triangle[i][j] = triangle[i-1][j] + triangle[i][j] 17 } else if j == (len(triangle[i]) - 1) { 18 triangle[i][j] = triangle[i-1][j-1] + triangle[i][j] 19 } else { 20 triangle[i][j] = min(triangle[i-1][j-1], triangle[i-1][j]) + triangle[i][j] 21 } 22 } 23 } 24 for _,k := range triangle[l-1] { 25 result = min(result, k) 26 } 27 return result 28} 29 30func min(a, b int) int { 31 if a > b { 32 return b 33 } 34 return a 35}
課後思考:如何自下而上求解?評論區留言吧!
注:本系列全部教程中都不會用到複雜的語言特性,你們不須要擔憂沒有學過go。算法思想最重要,使用go純屬本人愛好。同時,本系列全部代碼均在leetcode上進行過測試運行,保證其嚴謹性!