想必不少人還不知道動態規劃是能夠狀態壓縮的吧,通俗的講就是把維數變小,通常就是把二維數組降爲一維。維數變小意味着空間變小,速度還不變,不用空間換時間,這就是狀態壓縮的強大之處。java
以leetcode64題最小路徑和爲例,帶你們一步一步見識一下狀態壓縮這個小技巧面試
題意:給定一個包含非負整數的 m x n 網格 grid ,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和爲最小算法
說明:每次只能向下或者向右移動一步編程
示例1數組
輸入:grid = [[1,3,1],[1,5,1],[4,2,1]]數據結構
輸出:7函數
解釋:由於路徑 1→3→1→1→1 的總和最小測試
函數名:優化
public int minPathSum(int[][] grid)
題目的最基本的狀態轉移方程是
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];3d
意思是若是咱們在i, j 這個位置的話從能夠從兩個方向推出dp[i][j]的值,題目說明了每次只能向下或者向右移動一步,以下圖
完整代碼以下
class Solution { public int minPathSum(int[][] grid) { if (grid.length == 0) return 0; int n = grid.length; int m = grid[0].length; int[][] dp = new int[n][m]; dp[0][0] = grid[0][0]; //初始化 //從(0,0)一直向下走 for (int i = 1; i < n; i++) dp[i][0] = dp[i - 1][0] + grid[i][0]; //從(0,0)一直向右走 for (int j = 1; j < m; j++) dp[0][j] = dp[0][j - 1] + grid[0][j]; //狀態轉移 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]; } }
上面的測試用例運行後的dp數組以下
上面的代碼很直觀吧
下面的代碼是爲了推出狀態壓縮而寫的,原理和上面同樣,只是上面的dp[0][0]換成下面的dp[1][1]
我這樣子寫不是說要這樣子作,只是爲了方便你們理解狀態壓縮而已,基本思路徹底沒有變
先貼出dp數組結果以下
代碼以下
class Solution { public int minPathSum(int[][] grid) { if (grid.length == 0) return 0; int n = grid.length; int m = grid[0].length; int[][] dp = new int[n + 1][m + 1]; //初始化 for (int i = 0; i <= n; i++) dp[i][0] = Integer.MAX_VALUE; dp[1][1] = grid[0][0]; //初始狀態 //從(1,1)一直往右走 for (int j = 2; j <= m; j++) dp[1][j] = dp[1][j - 1] + grid[0][j - 1]; //狀態轉移 for (int i = 2; 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 - 1][j - 1]; return dp[n][m]; } }
接下來開始進行狀態壓縮
把二維降爲一維結果以下,採用的是直接投影的方法
投影也就是直接把dp[i][j]中i所在的那一維去掉
代碼的轉變過程以下
由
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1]
變成
dp[j] = Math.min(dp[j], dp[j - 1]) + grid[i - 1][j - 1]
解釋一下上面爲何這樣子就完成了
想要求出dp[i][j]須要先求出dp[i - 1][j]和dp[i][j - 1]
咱們發現二維的dp[i - 1][j]映射成了一維的dp[j],dp[i][j - 1]映射成了dp[j - 1]
所須要的信息都能直接用一維來表示
哪怕dp[j]被覆蓋了也沒事,緣由看下圖
因此
dp[j] = Math.min(dp[j], dp[j - 1]) + grid[i - 1][j - 1]
完整代碼以下:
class Solution { public int minPathSum(int[][] grid) { if (grid.length == 0) return 0; int n = grid.length; int m = grid[0].length; int[] dp = new int[m + 1]; //初始化 dp[0] = Integer.MAX_VALUE; dp[1] = grid[0][0]; //從(1,1)一直向右走 真的只是把上面的代碼中dp[i][j]的i那一維去掉 for (int j = 2; j <= m; j++) dp[j] = dp[j - 1] + grid[0][j - 1]; //這裏也是直接去掉i中的那一維 for (int i = 2; i <= n; i++) for (int j = 1; j <= m; j++) dp[j] = Math.min(dp[j], dp[j - 1]) + grid[i - 1][j - 1]; return dp[m]; } }
這題到這裏也就結束了
這題的狀態壓縮之因此簡單是由於沒有多個元素映射到同一個位置
若是以下圖所示的話
一旦映射就會有兩個狀態映射到同一個位置,這裏提示一下,此時也很簡單,只須要定義一個變量,來存儲那兩個映射到同一個位置的兩個變量中的其中一個變量就好了
後面我有空也會寫一下兩個狀態映射到同一位置的這種狀況
這裏只是大概幫助大家入一下門。之後你看到有兩個狀態映射到同一位置的狀態壓縮的代碼時,也能很輕鬆的讀懂
這或許也是爲何面試官那麼看重計算機基礎,不就是由於計算機基礎是一切的基石,計算機基礎穩了,上手其餘那不是手到擒來
圖片目前作的不是很美觀,之後會慢慢優化。
若是以爲有收穫,不妨花個幾秒鐘點個贊,歡迎關注個人公衆號玩編程地碼農,目前會不斷寫與java、數據結構與算法和計算機基礎相關的知識等。