組合問題的DP妙用

組合問題的DP妙用

本文將以幾個例子來講解。一般我們在使用dp時都是先證明最優性原理,本文的話就跳過這一部分了,因爲這個證明一般比較簡單

1.最長子序列問題

問題描述:給定一個序列,要求找出其中最長的子序列的長度;
例子:

輸入:5 2 8 6 3 6 9 7
輸出:4
解釋:最長子序列爲2 3 6 9或者2 3 6 7

分析:

我們這樣來定義子問題
設L(i)表示從a1到ai的最長子序列的長度,那麼有
①i=1或者沒有比ai小的aj(j<i),L(i)=1
②L(i)=max {L(j)+1}(當aj<ai時)

在下面代碼中我保存了每一個子問題對應狀態的最長子序列
代碼實現:

import org.junit.Test;
public class Main {
    int Max = 65535;
   @Test
   //x[][]數組,比如x[i]這一列就保存了L[i]對應的子序列
   //算法過程如下:
   /*
   1.初始化L[i]=1,x[i][0]=a[i]
   2.開始循環遍歷數組按照子問題的狀態方程來遞推
    */
    public void Test(){
        int a[]={5,2,8,6,3,6,9,7};
        getLongest(a);
    }
    void getLongest(int []a){
        int L[]=new int[a.length];
        int x[][]=new int[a.length][a.length];
        //初始化
        for(int i=0;i<a.length;i++){
           L[i]=1;
           x[i][0]=a[i];
        }
        for(int i=1;i<a.length;i++){
            for(int j=i-1;j>=0;j--){
                if(a[j]<a[i]&&L[j]+1>L[i]){
                    L[i]=L[j]+1;
                    for(int k=0;k<L[j];k++)
                        x[i][k]=x[j][k];
                    x[i][L[i]-1]=a[i];
                }
            }
        }
        //打印結果
        for(int i=0;i<L[a.length-1];i++){
            System.out.print(x[a.length-1][i]+" ");
        }
        System.out.println();
        System.out.println("最長子序列長度爲:"+L[a.length-1]);
    }
}

時間複雜度:O(n^2)

2.最長公共子序列

問題描述:

現在是給兩個序列找出X,Y序列的最長公共子序列

子問題分析:

L(i,j)表示{x1,x2,…,xi},與{y1,y2,…,yj}的最長公共子序列長度,那麼初始化子問題應該爲
L(0,0)=L(0,j)=L(i,0)=0;
易得狀態方程:
L(i,j)
①當xi=yj時,L(i,j)=L(i-1,j-1)+1
②當xi!=yj時,L(i,j)=max{L(i,j-1),L(i-1,j)};

當然光有長度是肯定不夠的,我們還需要保留對應的序列,這裏給出的辦法是:

若X與Y序列的大小分別爲m與n;
定義一個數組狀態數組s[m+1][n+1];
①若Xi=Yj,那麼s[i][j]=1,表示他下一個搜索的是L[i-1][j-1],對應的下一個狀態爲s[i-1][j-1]
②若Xi!=Yj且L[i-1][j]>=L[i][j-1],那麼s[i][j]=2,表示下一個搜索的是L[i-1][j],對應的下一個狀態爲s[i-1][j]
③若Xi!=Yj且L[i-1][j]<L[i][j-1],那麼s[i][j]=3,表示下一個搜索的是L[i][j-1],對應的下一個狀態爲s[i][j-1]
最後我們進行填充L[i][j]時把s[i][j]也進行記錄
結束後從s[m][n]出發回溯即可(回溯時如果s[i][j]==1說明爲需要的點)

代碼:

import org.junit.Test;
public class Main {
    int Max = 65535;
   @Test
    public void Test(){
        char[] X = {' ','a','b','c','b','d','b'};
        char[] Y = {' ','a','c','b','b','a','b','d','b','b'};
        char[] Z = new char[X.length];
        getLongest(X,Y,Z);
        for(int i=0;i<Z.length;i++){
            if(Z[i]!=0)
                System.out.print(Z[i]+" ");
        }
    }
    //該函數將x,y的最長公共子序列放入z中
    void getLongest(char x[],char y[],char[] z){
       int [][]L = new int[x.length][y.length];
       int [][]S = new int[x.length][y.length];
       //初始化
        for(int i=0;i<x.length;i++)
            L[i][0]=0;
        for(int j=0;j<y.length;j++)
            L[0][j]=0;
        //遍歷填充L[i][j]
        for(int i=1;i<x.length;i++)
            for(int j=1;j<y.length;j++){
                if(x[i]==y[j]){
                    L[i][j] = L[i-1][j-1]+1;
                    S[i][j] = 1;
                }else if(x[i]!=y[j]){
                    if(L[i-1][j]>=L[i][j-1]){
                        L[i][j]=L[i-1][j];
                        S[i][j] = 2;
                    }else{
                        L[i][j]=L[i][j-1];
                        S[i][j] = 3;
                    }
                }
            }
        //接下來回溯(從S[x.length][y.length]開始
        int i=x.length-1,j=y.length-1;
            int k = L[x.length-1][y.length-1]-1;
            //一直到邊界退出即可
            while(i>=1&&j>=1){
                if(S[i][j]==1){
                    z[k--]=x[i];
                    i--;
                    j--;
                }else if(S[i][j]==2){
                    i--;
                }else
                {
                    j--;
                }
            }
    }
}

結果:
在這裏插入圖片描述
如果你比較細心會發現X={a,b,c,b,d,b},Y={a,c,b,b,a,b,d,b,b}的最長公共子序列也可以爲
a,b,b,d,b 原因在於你的條件判斷,如果在複製S[i][j]時改爲

若Xi!=Yj且L[i-1][j]>L[i][j-1],那麼s[i][j]=3
若Xi!=Yj且L[i-1][j]<=L[i][j-1],那麼s[i][j]=2

就可以得到上面結論,這裏留給讀者自己嘗試。

3.揹包問題

這個問題在我的揹包九講專題有解答

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