上一篇講述了動態規劃入門級題目,代碼都是沒有優化的,如果沒有看過的讀者也沒關係,在下面會貼出這兩道題目的所有代碼,包括沒有優化的和優化之後的。感興趣的讀者可以先去看一下上一篇的題目,都是EASY級別的題目。
小白學習動態規劃:入門篇
今天主要是對上一篇博客的兩道題目進行優化,對於絕大多數利用動態規劃的算法題作優化時,個人認爲最重要的優化方法就是:畫DP的圖!找出狀態之間的依賴關係,將其它沒有依賴的狀態廢棄掉。
如果你不懂沒有關係,可以看以下例子,很好理解~
優化類型一:一維降變量
LeetCode70. 爬樓梯
問題描述:假設你需要爬樓梯,需要爬n階才能到達樓頂,每次可以爬1或2階,由多少種不同的方法可以爬到樓頂?
未優化前的代碼:
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n];
for (int i = 0; i < dp.length; i++) {
if(i == 0){ //第1階
dp[i] = 1;
}else if(i == 1){ //第2階
dp[i] = 2;
}else{ //第3階及以上
dp[i] = dp[i-1]+dp[i-2];
}
}
return dp[n-1];
}
}
首先,我們先分析dp的填充過程,觀察有什麼值是會在某個時間段後作廢(一直沒有使用到)
從上面幾幅圖可以很清晰地觀察到,從求第四階樓梯的爬樓梯方法數之後,每求下一階樓梯的方法數時,就會有多一個值被廢棄掉,而求某個狀態的值(爬樓梯的方法數)只與它的第(n-2)個狀態和第(n-1)個狀態[n >= 3]有依賴關係,所以在第(n-2)個狀態以前的值就被廢棄掉了。
所以,這個定義一維DP數組在某種程度上是浪費了內存空間的。
未優化前的狀態轉移方程:
優化的過程可以觀察下圖:
顏色的含義(幫助你理解整個過程):
藍色:初始化的狀態值
黃色:得出下一個狀態的狀態值
紅色:將當前狀態的第(n-1)個狀態轉化爲第(n-2)個狀態
綠色:將第n個狀態轉化爲第(n-1)個狀態
動態規劃過程:
求出一個新的狀態時,將當前狀態的第(n-1)狀態更新爲第(n-2)個狀態,將當前狀態更新爲第(n-1)個狀態,繼續求下一個新的狀態,直到循環結束。
優化後的代碼:
class Solution {
public int climbStairs(int n) {
if(n <= 2){return n;}
int dp0 = 1;
int dp1 = 2;
for(int i = 3; i <= n; i++){
int result = dp0 + dp1;
dp0 = dp1;
dp1 = result;
}
return dp1;
}
}
優化類型二:二維降一維
LeetCode62. 不同路徑
問題描述:一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲“Start” )。
機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記爲“Finish”)。
問總共有多少條不同的路徑?
說明:m 和 n 的值均不超過 100。
未優化前的代碼:
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++){
//上邊界
if(i == 0 && j >= 0){dp[i][j] = 1;continue;}
//左邊界
if(j == 0 && i >= 0){dp[i][j] = 1;continue;}
//其它情況
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
觀察未優化前的DP矩陣:
狀態轉移方程:
當填充第二行時,情況是這樣的:
當填充第三行時,情況是這樣的:
**細心的我們會發現:**第一行數據已經沒有用處了,它的存在與否不影響我們求第三行的數據。所以,我們只需要保存上一行數據,就可以得出下一行的數據,並且每求出下一行的一個數據時,都可以捨棄掉上一行的那一個數據,所以只需要一維數組保存一行數據就可以求出下一行的數據。
如果你不是很懂,那麼看下面幾幅圖就會秒懂~
注意左邊數組行下標一直爲0,表示該數組本質上只有一行
原本二維dp數組的狀態轉移方程爲:
在圖中從位置上表現出來的結果是正確的,但是!!!重點來了!
注意看!我們會發現狀態轉移方程轉化爲:
DP的空間複雜度由O(m * n) 降低爲O(m)
最後數組就會呈現出下圖得到狀態:
優化後的一維dp代碼:
class Solution {
public int uniquePaths(int m, int n) {
if(m <= 1 || n <= 1){return 1;}
int[] dp = new int[n];
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(j == 0){dp[j] = 1;continue;}
dp[j] = dp[j-1] + dp[j];
}
}
return dp[dp.length - 1];
}
}
總結
這兩道題目雖然簡單,但是它們是動態規劃入門的必做題目中的兩道,掌握這兩道題目的思想和優化技巧是非常重要的,優化DP的核心在於:畫圖
掌握畫圖這一種優化方法,尋找值的依賴關係,觀察能否將空間複雜度降低,DP的核心本質上是用空間換取時間的算法,在面試中會遇到許多面試官提出優化方法的場景,掌握這一種技巧,刷多一些相關的題目,自然會熟能生巧!