DP(動態規劃)入門

談到動態規劃,最經典的當然是揹包問題,可以百度下<<揹包九講>>。
但是這裏不準備從揹包問題講起,主要是覺得用揹包問題來講DP的思想,還不夠通俗易懂。

先來看一個金典的算法題:
爬臺階問題
有n個臺階,你每次可以爬1階或2階,問:爬到頂總共有多少種爬法?
例子: n = 3
第一種:1 1 1
第二種:2 1
第三種:1 2
總共有3種爬法。

解法一:遞歸
代碼如下:

public int climbStairs(int n) {
    return (n<=2) ? n : climbStairs(n-1) + climbStairs(n-2);
}

如果n=1,只有一種爬法。
如果n=2,有[(1,1),(2)]兩種爬法
如果n>2,分兩種情況:

  • 第一種,最後一次爬了1階,climbStairs(n-1)
  • 第一種,最後一次爬了2階,climbStairs(n-2)

所以n>2時,climbStairs(n) = climbStairs(n-1) + climbStairs(n-2);

遞歸的思想是:至上而下,想求climbStairs(n) ,轉化爲求climbStairs(n-1)和climbStairs(n-2)的問題

解法二:DP

public int climbStairs(int n) {
    if(n <= 2)  return n;
    //dp[i] = m ,表示高度爲i的臺階,有m種爬法
    int[] dp= new int[n+1];
    
    dp[1] = 1; 
    dp[2] = 2;
    
    for(int i=3; i <= n ; i++)
        dp[i] = dp[i-1] + dp[i-2];

    return dp[n];
}

比如這裏n=4,數組dp的長度爲5,值爲:
dp = [0,1,2,3,5]
dp[4]=5,表示高度爲4的臺階有5種爬法。

DP的思想是至下而上的,想求dp[n]需要把前面從0到n-1的值全部求出來,而且是從0開始
DP的關鍵是能否通過之前求的值,推導出當前的值 也就是這行代碼:
dp[i] = dp[i-1] + dp[i-2]
如果dp[i]無法通過之前的值求得,那麼就無法使用dp求解。
dp[i] = dp[i-1] + dp[i-2]狀態轉移方程。DP求解的關鍵就是找到合適的狀態轉移方程
由於這個問題比較簡單,這個狀態轉移方程很容易理解。

與上一種遞歸解法相比,它的時間複雜度肯定要低些,但是他需要一個長度爲n+1的數組輔助,所以空間複雜度爲O(n+1)。

解法三:優化DP的空間複雜度

public int climbStairs(int n) {
    if(n <= 2)
        return n;

    int first = 1;
    int second = 2;
    
    for(int i=3; i <= n ; i++){
        int third = first + second;
        first = second;
        second = third;
    }
    
    return second;
}

對比上面的DP,這裏應該很容易理解,由於我們需要的是dp[n],而dp[0~n-1]我們沒必要一直存着,空間複用就行。優化後,這裏的空間複雜度爲O(1)。

從n=4,dp = [0,1,2,3,5]的例子不難看出,[1,2,3,5]其實就是一個斐波那契數列。所以這個題其實就是求解斐波那契數列。以上三種均不是最優解。存在時間複雜度爲O(log(n))的解法,感興趣可以去leetcode上去看看矩陣的解法

推薦另一道比較有趣的DP算法題:house robber

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章