算法-動態規劃DP小記

算法-動態規劃DP小記

動態規劃算法是一種比較靈活的算法,針對具體的問題要具體分析,其宗旨就是要找出要解決問題的狀態,而後逆向轉化爲求解子問題,最終回到已知的初始態,而後再順序累計各個子問題的解從而獲得最終問題的解。html

關鍵點就是找到狀態轉移方程和初始邊界條件,說白了就是要找到「遞推公式」和初始值,而後計算時保存每一步中間結果,最後累加判斷獲得結果。java

0.求數組最值

求數組最值方法不少,這裏使用動態規劃的思想來嘗試處理,以便更好地理解DP的思想。爲了方便這裏假設數組a[i]大小爲n,要找n個數當中的最大值。算法

設dp[i]表示第0...i個數的最大值,dp[i-1]表示第0...i-1個數的最大值,因此求前i個數的最大值時,已經知道前i-1個是的最大值是dp[i-1],那麼只須要比較dp[i-1]和第i個數誰大就知道了,即dp[i] = max(dp[-1], a[i])。數組

public int max(int[] a){
        int len = a.length;
        int[] dp = new int[len];
        dp[0] = a[0];
        for(int i=1; i<len; i++){
            dp[i] = (dp[i-1] > a[i]) ? dp[i-1] : a[i];
        }
        return dp[len-1];
    }

1.求最大公共子序列長度

給定一個字符串,想要刪掉某些字符使得最後剩下的字符構成一個迴文串(左右對稱的字符串,如abcba),問最少刪掉多少個字符可得到一個最長迴文串。post

/**
      * 本題求迴文串最大長度就轉化爲求兩個字符串的最長公共子序列(不必定連續)
      * 策略:字符串能夠看作是字符序列,即字符數組。
      *      好比有序列A=a0,a1,a2...an;有序列B=b0,b1,b2,b3...bm;設A序列和B序列的公共子序列爲C=c0,c1,c2,c3...ck。
      *      設L[][]爲公共子序列C的長度,L[i][j]的i、j分別表示A、B序列的字符下標,L[i][j]含義是A序列a0、a一、a2...ai和B序列b0、b一、b二、
      *      ...bj的公共子序列的長度。
      *     
      *      1)若是A序列的i字符和B序列的j字符相等,那麼就有ck=ai=bj,公共子序列C的長度L[i][j]=L[i-1][j-1]+1。
      *      2)若是A序列的i字符和B序列的j字符不相等,若ai != ck則C爲a0...ai-1和b0...bj的最長子序列,若bj != ck則C爲a0...ai和b0...bj-1的最長子序列,
      *         因此此時公共子序列長度爲L[i][j] = max(L[i][j-1], L[i-1][j])。
      */
    public static int lcs(String s){
        if (s == null  ) {
            return -1;
        }
        String rs = new StringBuilder(s).reverse().toString();
        char[] chars1 = s.toCharArray();
        char[] chars2 = rs.toCharArray();//得到反序的字符串
        int n = chars1.length;
        int[][] dp = new int[n+1][n+1];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(chars1[i] == chars2[j]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else {
                    dp[i][j] = dp[i][j-1] > dp[i-1][j] ? dp[i][j-1] : dp[i-1][j];
                }
            }
        }
        return n - dp[n][n];

    }

2.硬幣湊錢問題

只有面值爲1元、3元、5元的硬幣,數量足夠。如今要湊夠n元,求須要的最少硬幣枚數。ui

/**
     *  
     * @param n 目標總錢數
     * @param coins 硬幣數組【1,3,5】
     * @return 返回湊夠n元須要的最少硬幣數
     */

    public static int getLeastCoinAmount(int n, int[] coins){
        if (coins == null || n < 0) {
            return -1;
        }
        if (n == 0){
            return 0;
        }
        int[] dp = new int[n+1]; //dp[i]=j表示湊夠i元最少須要j枚硬幣。數組長度設爲(n+1)保證能夠訪問dp[n]。
        dp[0] = 0;
        for (int i = 1; i <= n; i++) {
            dp[i] = Integer.MAX_VALUE;
        }

        int coinValue = 0;
        for (int i = 1; i <= n; i++) {//問題規模從小到大,直到達到目標面值
            for (int j = 0; j < coins.length; j++) {//遍歷全部面值的硬幣,j表示硬幣面值的下標
                coinValue = coins[j];
                if (i - coinValue >= 0 && 1 + dp[i-coinValue] < dp[i]){ //當前方案的硬幣數更少,則使用當前方案
                    dp[i] = 1 + dp[i-coins[j]];
                }
            }

        }
        return dp[n];
    }

3.最長非降子序列

一個序列有N個數:A[1],A[2],…,A[N],求出最長非降子序列的長度。.net

/**
     * 
     * 定義d(i)表示前i個數中"以A[i]結尾"的最長非降子序列的長度。
     * 對序列A1...Ai,找到的最長子序列長度d[i]分兩種狀況:
     * (1)包含最後一個數Ai,即d[i]=max{d[j]+1}(1<=j<i且Aj<=Ai),知足條件的Aj可能會有多個,選最大的d[j],若是Aj都大於Ai則d[j]=0;
     * (2)不含最後一個數,即d[i]=d[i-1]
     *
     * 綜上:d[i] = max{d[i-1], max{d[j]+1}}
     */
    public static int longestIncreasingSubsequence(int[] a){
        if (a == null) {
            return -1;
        }
        if (a.length < 1){
            return 0;
        }
        int len = a.length;
        int[] dp = new int[len];//dp[i]系統自動初始化爲0
        dp[0] = 1;
        for (int i = 1; i < len; i++) {//迭代,求序列0...len-1的最長子序列長度
            for (int j = 0; j < i; j++) {//尋找Ai以前的序列,看是否有不大於Ai的數字Aj
                if (a[j] <= a[i] && dp[i] < dp[j] + 1){//假設最長子序列包含最後一個數
                    dp[i] = dp[j] + 1;
                }
            }
            //尋找Ai以前的序列若是Ai都小於Aj,此時dp[i]並無被修改仍爲初始值0。因此包含最後一個數的最長子序列就只有最後一個數自身,長1
            dp[i] = Math.max(1, dp[i]);
            //至此,已經求出了包含最後一個數的最長子序列的長度,和不包含最後一個數的最長子序列長度比較,取最大值爲當前的最大長度
            dp[i] = Math.max(dp[i], dp[i-1]);
        }
        return dp[len-1];

    }

4.經典01揹包問題

01揹包問題:一個承重(或體積)爲W的揹包,可選物品有n個,第i個物品分別重w[i]和價值v[i],每一個物品只能拿或不拿,求揹包可放物品的最大價值。code

/**
     * 
     * 策略:這裏的關鍵制約因素是揹包只能承重w,並且每放入一個物品其承重就會減小。
     *      所以定義maxValue=V[i][j],數組表示目前可選物品有i個:0、1...i-1,揹包承重(剩餘的存放重量)爲j的最大價值。
     *      如今假設已經知道了(i-1)個物品且剩餘承重爲j的最大價值V[i-1][j],那麼考慮準備放入第i個物品的狀況:
     *     (1)若是第i個物品的重量大於揹包的剩餘承重w_i>j,顯然放不下了,因此此時V[i][j]=V[i-1][j];
     *      (2)w_i<=j,顯然能夠放下第i個物品,物品能夠放得下,可是必定要裝進來嗎?若是裝進的物品價值較低且較重,無疑會影響後續物品的裝入狀況。
     *        因此還要考慮要不要放進來的子問題,V[i][j]=max{vi+V[i-1][j-wi], V[i-1][j]}。
     *
     * @param W
     * @param n
     * @param w
     * @param v
     * @return
     */
    public static int knapsack(int W, int n, int[] w, int[] v){
        if ( W < 1 || n < 1 || w == null || v == null) {
            return -1;
        }
        int[][] dp = new int[n+1][W+1]; //可選的物品最多能夠有n個,因此行數設爲n+1。最大承重是W,因此列設爲W+1。
        int index = 0;
        for (int i = 1; i <= n; i++) { //物品數確定是從1開始。dp[0][j]系統初始化爲0.
            index = i-1;
            for (int j = 1; j <= W ; j++) {//能裝進的重量確定是從1開始。dp[i][0]系統初始化爲0.
                if (w[index] > j){
                    dp[i][j] =  dp[i-1][j];
                }else {
                    dp[i][j] =  Math.max(dp[i - 1][j - w[index]] + v[index], dp[i - 1][j]);
                }
            }

        }

        //找出是哪些物品放入揹包
        boolean[] isTaken = new boolean[n];//標記是否放入揹包裏
        for (int i = n; i > 0 ; i--) {
            if (dp[i][W] != dp[i-1][W]){
                isTaken[i-1] = true;//裝入
                W -= w[i-1];//裝入以後揹包的承重減小
                System.out.println(i-1);
            }
        }
        return dp[n][W];//返回n個物品承重爲W時的最大價值
    }


}

推薦文章:htm

動態規劃:重新手到專家blog

動態規劃解決01揹包問題(java實現)

相關文章
相關標籤/搜索