【算法】動態規劃問題集錦與講解

博客新址,這裏更有趣git

動態規劃

代碼實如今https://github.com/Jensenczx/...
維基百科對動態規劃的定義github

動態規劃(英語:Dynamic programming,簡稱DP)是一種在數學、計算機科學和經濟學中使用的,經過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法。動態規劃經常適用於有重疊子問題[1]和最優子結構性質的問題,動態規劃方法所耗時間每每遠少於樸素解法。動態規劃背後的基本思想很是簡單。大體上,若要解一個給定問題,咱們須要解其不一樣部分(即子問題),再合併子問題的解以得出原問題的解。算法

一般許多子問題很是類似,爲此動態規劃法試圖僅僅解決每一個子問題一次,從而減小計算量:一旦某個給定子問題的解已經算出,則將其記憶化(en:memoization)存儲,以便下次須要同一個子問題解之時直接查表。這種作法在重複子問題的數目關於輸入的規模呈指數增加時特別有用.數組

簡言之動態規劃的思路是經過尋找最優子結構同時記錄最優結構,從而將複雜的大問題轉化爲小問題的求解過程,最近針對於動態規劃作了些練習,找到些解題的思路和感受,下面針對於幾個問題來逐步的分析下動態規劃。佈局

動態規劃問題實例

解決動態規劃類問題,分爲兩步:1.肯定狀態,2.根據狀態列狀態轉移方程
肯定該狀態上能夠執行的操做,而後是該狀態和前一個狀態或者前多個狀態有什麼關聯,一般該狀態下可執行的操做一定是關聯到咱們以前的幾個狀態。code

數字三角形

問題描述
給定一個數字三角形,找到從頂部到底部的最小路徑和。每一步能夠移動到下面一行的相鄰數字上。遞歸

[2],
[3,4],
[6,5,7],
[4,1,8,3]字符串

從頂到底部的最小路徑和爲11 ( 2 + 3 + 5 + 1 = 11)。get

若是採用樸素算法,咱們須要記錄每次的行走軌跡,而後對其大小進行比較,最終得出結果,行走軌跡的統計是呈現指數遞增的,因此咱們要採用動態規劃的方法來解決。根據咱們的解決方法,先肯定狀態,也就是每次向下走的一步即爲一個狀態,而後是狀態轉移方程,從上一個狀態到下一個狀態,若是肯定最優,當前狀態的結果,取決於上一個狀態,找到上一個狀態,而後肯定上一個狀態到當前狀態轉移的方程。記錄下每個狀態,咱們經過一個二維數組來實現。博客

public int minimumTotal(int[][] triangle) {
        // write your code here
        if(triangle==null||triangle.length==0)
            return 0;
        int len = triangle.length;
        //用來記錄每一步的狀態
        int [][] cost = new int[len][len];
        cost[0][0]=triangle[0][0];
        for(int i=1; i<len; i++){
            for(int j=0; j<triangle[i].length; j++){
            //計算上一個狀態的時候,防止出現越界問題
                int lower = max(0,j-1);
                int upper = min(j,triangle[i-1].length-1);
            //狀態轉移方程
                cost[i][j]= min(cost[i-1][lower],cost[i-1][upper])+triangle[i][j];
            }
        }
        int minCost = Integer.MAX_VALUE;
        for(int k=0; k<triangle[len-1].length; k++){
            minCost = min(minCost,cost[len-1][k]);
        }

       return minCost;
    }

揹包問題兩講

這裏解決了兩張揹包問題,一個是肯定最多能夠裝的下多少的揹包盛放物品問題,還有一個是揹包中放置的物品具備價值,要來肯定其價值爲多少。解決方法都是經過動態規劃來解決。

揹包問題1
問題描述
在n個物品中挑選若干物品裝入揹包,最多能裝多滿?假設揹包的大小爲m,每一個物品的大小爲A[i]

首先尋找狀態,肯定將什麼做爲狀態,記錄狀態,有揹包和物品,物品有放和不放兩種狀態,放置的時候可能會對應各類容量,當前的容量下能夠放置進的最多的物品取決於上一個物品放置時在該狀態下所可以達到的最大狀態和當前物品的的大小,這樣咱們在最後,就能夠獲得每種容量下,所能放置的物品的最大數量。

public int backPack(int m, int[] A) {
        // write your code here
           if (A == null || 0 == A.length || m == 0)
               return 0;
          int len = A.length;
          //初始化了一個數組,
          int[][]  sum = new int[len][m+1];
          for(int i=0;i<len;i++){
               sum[i][0] = 0;
          }
          for(int j=0;j<m+1;j++){
               if(j>=A[0]){
                    sum[0][j] = A[0];
               }
          }         
          for(int i=1;i<len;i++){
               for(int j=1;j<m+1;j++){
                    if(j>=A[i]){
                         sum[i][j] = max(sum[i-1][j], sum[i-1][j-A[i]]+A[i]);
                    }else{
                         sum[i][j] = sum[i-1][j];
                    }
               }
          }
          return sum[len-1][m];
    }

揹包問題2
問題描述
給出n個物品的體積A[i]和其價值V[i],將他們裝入一個大小爲m的揹包,最多能裝入的總價值有多大?

考慮到價值問題,狀態不發生變化,只是對於狀態咱們所記錄的內容方式變化,咱們如今記錄的是其價值,而不是其放置的物品的大小。

public int backPackII(int m, int[] A, int V[]) {
        // write your code here
        if(m==0||A==null||V==null||0==A.length)
            return 0;
        int len = A.length;
        int [][]val = new int[len][m+1];
        for(int i=0;i<len; i++){
            val[i][0]=0;
        }
        for(int i=0; i<m+1; i++){
            if(i>=A[0])
                val[0][i]=V[0];
        }
        for(int i=1; i<len; i++){
            for(int j=1;j<m+1; j++){
                if(j>=A[i]){
                    val[i][j] = max(val[i-1][j],val[i-1][j-A[i]]+V[i]);
                }else{
                    val[i][j]=val[i-1][j];
                }
            }
        }
        return val[len-1][m];
    }

公共子序列,公共子串問題

公共子串
給出兩個字符串,找到最長公共子串,並返回其長度

狀態,字符串的每一位對應另外一個字符串的每個位置,所以經過一個二維數組來表示這每個狀態位,而後是找狀態轉移方程,轉移方程即爲其前一個位置的前一個的比對的結果累計當前的結果,若是相同則加1,不然爲0

public int longestCommonSubstring(String A, String B) {
        // write your code here
          if(A==null||B==null||A.length()==0||B.length()==0)
            return 0;
        int lenOfA = A.length();
        int lenOfB = B.length();
        //狀態記錄結構
        int[][] longSubString = new int[lenOfB][lenOfA];
        int max = 0;
        for(int i=0; i<lenOfA; i++){
            if(B.charAt(0)==A.charAt(i)){
                longSubString[0][i] = 1;
                max = 1;
            }
        }
        for(int i=1; i<lenOfB; i++){
            for(int j=0; j<lenOfA; j++){
            //狀態轉移
                if(B.charAt(i)==A.charAt(j)){
                    if(j-1>=0)
                    longSubString[i][j] = longSubString[i-1][j-1]+1;
                    else 
                         longSubString[i][j]=1;
                    max = Max(longSubString[i][j],max);
                }
            }
        }
        return max;
    }

公共子序列
給出兩個字符串,找到最長公共子序列(LCS),返回LCS的長度。

子序列和子串的區別在於,其值不是僅僅取決於其上一個位置的對應於比對的位置的狀態,而是要尋找最大的前面的狀態值中最大的一個。

public int longestCommonSubsequence(String A, String B) {
        // write your code here
        if(A==null||B==null||A.length()==0||B.length()==0)
            return 0;
        int lenOfA = A.length();
        int lenOfB = B.length();
           int [][] subsLen = new int[lenOfB][lenOfA];
           int max=0;
           for(int i=0; i<lenOfA; i++){
               if(A.charAt(i)==B.charAt(0)){
                   subsLen[0][i]=1;
                   max = 1;
               }
           } 
           for(int i=1; i<lenOfB; i++){
               for(int j=0; j<lenOfA; j++){
                   if(A.charAt(j)==B.charAt(i)){
                       subsLen[i][j]=Max(subsLen,i-1,j-1)+1;
                       if(subsLen[i][j]>max)
                           max = subsLen[i][j];
                   }
               }
           }
           return max;

    }
    
     public int Max(int[][] array,int end1,int end2){
        if(end2<0)
            return 0;
        int max = array[0][0];
        for(int i=0; i<=end1; i++){
            for(int j=0; j<=end2; j++){
                if(array[i][j]>max)
                    max = array[i][j];
            }
        }
        return max;
    }

打劫房屋

問題描述
假設你是一個專業的竊賊,準備沿着一條街打劫房屋。每一個房子都存放着特定金額的錢。你面臨的惟一約束條件是:相鄰的房子裝着相互聯繫的防盜系統,且 當相鄰的兩個房子同一天被打劫時,該系統會自動報警。
給定一個非負整數列表,表示每一個房子中存放的錢, 算一算,若是今晚去打劫,你最多能夠獲得多少錢 在不觸動報警裝置的狀況下。

咱們能夠在經過一個數組來記錄下來,咱們在每一個位置打劫,所能獲得的錢,在求下一個狀態的時候,遍歷前面的與其相隔的全部狀態,而後找到一個最大的,可是複雜度比較到達到了n2,空間複雜度爲n,對於狀態,咱們須要記錄的只有其前一個,還有與其相隔的全部狀態的最大值,所以經過兩個數字來表示便可。具體轉化方式見代碼實現。

public long houseRobber(int[] A) {
        // write your code here
          if(A==null||A.length==0)
            return 0;
        int len = A.length;
        if(len==1)
            return A[0];
        long max1 = A[0];
        long max2 = A[1];
        for(int i=2; i<len; i++){
        long tmp = max2;
            max2 = max1+A[i];
            max1 = tmp;
            //在計算最大值的時候的一個轉化
            if(max2<max1)
                max2 = max1;
        }
        return Max(max1,max2);
    }
       public long Max(long a,long b){
        return a>b?a:b;
    }

編輯距離

題目描述
給出兩個單詞word1和word2,計算出將word1 轉換爲word2的最少操做次數。

你總共三種操做方法:

  • 插入一個字符

  • 刪除一個字符

  • 替換一個字符

三種操做,所以咱們在一個狀態上面能夠進行三種狀態的變化,肯定每個狀態,經過第二個字符串和第一個字符串的每個位置的對應做爲一個狀態,處在該狀態上,咱們能夠進行的操做,改,進行改操做,那麼與之關聯的前一個狀態是其前一個字符對應另外一個字符串的當前對應的前一個字符,增,則是說當前字符串的當前位對應到前一個字符串的前一個位置,刪,則爲當前字符串的當前位對應前一個字符串的前一個位置。爲了增長一個增的位置,須要咱們在其前面,因此咱們在兩個字符串的開始處設置一增長的位置。

public class Solution {
    /**
     * @param word1 & word2: Two string.
     * @return: The minimum number of steps.
     */
    public int minDistance(String word1, String word2) {
        // write your code here
       if(word1==null||word2==null)
            return 0;
        int len1 = word1.length();
        int len2 = word2.length();
        if(len1==0||len2==0)
            return Max(len2,len1);
        int [][]dp = new int[len2+1][len1+1];
        for(int i=0; i<=len1; i++){
            dp[0][i]=i;
        }
        for(int i=0; i<=len2; i++){
            dp[i][0]=i;
        }
           for(int i=1; i<=len2; i++){
               for(int j=1; j<=len1; j++){
                   if(word2.charAt(i-1)==word1.charAt(j-1)){
                   //狀態轉化,分別別是刪,增,改
                       dp[i][j] = Min(Min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]);
                   }else{
                       dp[i][j] = Min(Min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1);
                   }
               }
           }
           return dp[len2][len1];
    }
    
    public int Max(int a,int b){
        return a>b?a:b; 
    }

    public int Min(int a,int b){
        return a<b?a:b; 
    }
}

N皇后問題

n皇后問題是將n個皇后放置在n*n的棋盤上,皇后彼此之間不能相互攻擊。
給定一個整數n,返回全部不一樣的n皇后問題的解決方案。

對於n皇后的問題,下一個皇后的佈局位置將與以前的全部王后佈局有關,所以經過動態規劃,沒安置一個皇后就做爲一個狀態,而後判斷以前的已經安放的全部皇后的狀態,肯定是否能夠按這一個皇后,經過遞歸的方式實現。

public int num1(int n){
    if(n<1)
        return 0;
    int[] record = new int[n];
    return process(0,record,n); 
}

public int process(int i,int []record,int n){
    if(i==n)
        return 1;
    int res = 0;
    for(int j=0; j<n; j++){
        if(isValid(record,i,j)){
            record[i]=j;
            res+=process(i+1,record,n);
        }
    }
    return res;
}

public boolean isValid(int[]record,int i,int j){
    for(int k=0; k<i; k++){
        if(j==record[k]||Math.abs(record[k]-j)==Math.abs(i-k)){
            return false;
        }
    }
    return true;
}

後記

對於動態規劃的更多問題,將會繼續更新,陸續也會寫一些貪心算法等常見的算法類型。

相關文章
相關標籤/搜索