动态规划——矩阵中的最短路径长度
文章出處:極客時間《數據結構和算法之美》-作者:王爭。該系列文章是本人的學習筆記。
題目
假設我們有一個 n 乘以 n 的矩陣 w[n][n]。矩陣存儲的都是正整數。棋子起始位置在左上角,終止位置在右下角。我們將棋子從左上角移動到右下角。每次只能向右或者向下移動一位。從左上角到右下角,會有很多不同的路徑可以走。我們把每條路徑經過的數字加起來看作路徑的長度。那從左上角移動到右下角的最短路徑長度是多少呢?
回溯法
從 (0, 0) 走到 (n-1, n-1),每一步都有向下或者向右2種選擇方式。當走到 (n-1, n-1)停止。
寫回溯代碼中,遞歸函數f的參數,僅僅與狀態有關,與狀態無關的變量(例如n、m、grid)都作為類的實例變量保存起來。
我們根據上面這個特殊的例子,把回溯求解問題的遞歸樹畫出來。
遞歸樹的每一個節點表示一個狀態,用(i,j,currentPathSum),表示當前將要處理第i行第j列的數據,在處理之前已經走過的路徑長度是currentPathSum。
在遞歸樹上能看到雖然(i,j,currentPathSum)重復的不存在,但是這道題目要求的是到達(n-1,n-1)的時候最短路徑長度,所以在處理的時候只需要知道到達(i,j)的時候最短的路徑長度是多少,其余節點就無需向下擴散了。例如f(1,2,9),f(1,2,5),f(1,2,3)。那只需要保留f(1,2,3),f(1,2,9),f(1,2,5)無需向下擴散。因為達到(1,2)節點之后,仍然是向右、向下走,與currentPathSum等于5、9,3都無關。這樣就保證了節點不會指數級增長。
對遞歸樹剪枝——加緩存
int[][] memo,memo[i][j]=currentSum。當調用f(1,2,3)的時候,如果memo[1][2]值為0 ,那就設置memo[1][2]=3,向下計算。當遇到f(1,2,9)的時候,發現memo[1][2]=3,而9>3,則不再計算。
代碼的變化就是加了判斷:memo[i][j]==0∣∣memo[i][j]>currentSummemo[i][j]==0 || memo[i][j]>currentSummemo[i][j]==0∣∣memo[i][j]>currentSum。
對遞歸樹剪枝——動態規劃
狀態轉移表
接下來我們按照這種方式,計算狀態轉移表。我們畫出一個二維狀態表,表中的行、列表示棋子所在的位置,表中的數值表示從起點到這個位置的最短路徑。注意:這里狀態表的值的含義與遞歸樹中的值的含義發生了變化。我們看到在遞歸樹中到達最后一個位置,還需要currentPathSum+matrix[i][j]。但在表中是不需要的。
如果把表定義為int[][] dp ,dp[i][j]=到達(i,j)位置的最短路徑。我們想要的返回值就是dp[n-1][m-1]。
我們按照決策過程,通過不斷狀態遞推演進,將狀態表填好。為了方便代碼實現,我們按行來進行依次填充。
初始化這一步是很重要的。
對dp[0][0]=grid[0][0]。
對于第0行,大于0的列,只能從左側的位置轉移到當前位置:dp[0][j-1]+grid[0][j]。
對于第0列,只能從上面的位置轉移到當前位置:dp[i][0]=dp[i-1][0]+grid[i][0]。
代碼:
public int minDistDP(int[][] matrix ,int n){int[][] states = new int[n][n];//第一行for(int j=0;j<n;j++){if(j==0){states[0][j] = matrix[0][j];}else{states[0][j] = states[0][j-1]+matrix[0][j];}}//第一列for(int i=1;i<n;i++){states[i][0] = states[i-1][0]+matrix[i][0];}for(int i=1;i<n;i++){for(int j=1;j<n;j++){states[i][j] = Math.min(states[i-1][j],states[i][j-1])+matrix[i][j];}}return states[n-1][n-1];}狀態轉移方程
定義int[][] dp ,dp[i][j]=到達(i,j)位置的最短路徑。
我們知道可以從(i-1,j)或者(i,j-1)兩個狀態到達(i,j)。
從 (i-1,j)到達(i,j),路徑和需要增加grid[i][j],也就是說dp[i][j]=dp[i-1][j]+grid[i][j]。
從(i,j-1)到達(i,j),路徑和需要增加grid[i][j],也就是說dp[i][j]=dp[i][j-1]+grid[i][j]。
那么轉移方程就是
初始化:
對dp[0][0]=grid[0][0]。 對于第0行,大于0的列,只能從左側的位置轉移到當前位置:dp[0][j-1]+grid[0][j]。 對于第0列,只能從上面的位置轉移到當前位置:dp[i][0]=dp[i-1][0]+grid[i][0]。實現代碼:
public int minPathSum(int[][] grid) {if(grid==null || grid.length==0) return 0;int n = grid.length;int m = grid[0].length; int[][] dp = new int[n][m];for(int j=0;j<m;j++){dp[0][j] = (j==0?grid[0][0]:dp[0][j-1]+grid[0][j]);}for(int i=1;i<n;i++){dp[i][0] = dp[i-1][0]+grid[i][0];}for(int i=1;i<n;i++){for(int j=1;j<m;j++){dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];}}return dp[n-1][m-1];}類似題目
可以用相同的思路處理 LeetCode 322 零錢兌換。
總結
以上是生活随笔為你收集整理的动态规划——矩阵中的最短路径长度的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux命令面试突击
- 下一篇: 基础入门_Python-内建函数.运维开