適合動態規劃(DP,dynamic programming)方法的最優化問題有兩個要素:最優子結構和重疊子問題。html
最優子結構指的是最優解包含的子問題的解也是最優的。python
重疊子問題指的是每次產生的子問題並不老是新問題,有些子問題會被重複計算屢次。ios
因此能夠用以下步驟來解決:算法
接下來咱們分別在最長公共子串、最長公共子序列和01揹包問題中演示如何應用上面的套路。數組
開始討論以前要明白子串和子序列的區別在於:子串要求在原字符串中是連續的,而子序列則沒有要求[1]。例如, 字符串 s1=abcde,s2=ade,則LCStr=de,LCSeq=ade。post
其中LCStr表示Longest Common Substring 。LCSeq表示Longest Common Subsequence。測試
問題定義優化
對於兩個字符串,請設計一個時間複雜度爲O(m*n)的算法(這裏的m和n爲兩串的長度),求出兩串的最長公共子串的長度。 給定兩個字符串A和B,同時給定兩串的長度n和m。spa
測試樣例:"1AB2345CD",9,"12345EF",7設計
返回:4
假設有字符串x,y
f(i,j)表示x中以x[i]結尾的子串集合和y中以y[j]結尾的子串集合,二者交集中最長串的長度。(i和j都在合法範圍內)
好比x="caba",以a[2]結尾的子串集合以下: { "b","ab","cab"}
當x[i]!=y[j]時,毫無疑問,交集爲空集,此時f(i,j) = 0
當x[i]==y[j]時,f(i,j) = f(i-1,j-1) + 1
當i=0或者j=0時,f(i,j) = 0 //遞歸出口,邊界狀況
有了遞歸公式就能夠設計dp表格了,假設x="caba" , y="bab" , 二維數組dp[i][j]對應f(i,j)。當x[i]==y[j]時,dp[i][j]=dp[i-1][j-1]+1
從左到右,從上到下計算獲得dp表格:
一樣的本題的dp表格以下
class LongestSubstring { public: int findLongest(string A, int n, string B, int m) { //初始化n*m matrix vector<vector<int> > dp(n,vector<int>(m,0)); int res=0; for(int i=0;i<n;i++)//x串 { for(int j=0;j<m;j++)// y串 { if(A.at(i)==B.at(j)) { if(i==0||j==0) dp[i][j]=1; else dp[i][j]=dp[i-1][j-1]+1; // udpate maximum if(res<dp[i][j]) res=dp[i][j]; } } } return res; } };
對於兩個字符串,請設計一個高效算法,求他們的最長公共子序列的長度, 這裏的最長公共子序列定義爲:有字符串A的下標序列U1,U2,U3...Un和字符串B的下標序列V1,V2,V3...Vn,其中Ui < Ui+1,Vi < Vi+1。且A[Ui] == B[Vi]。
給定兩個字符串A和B,同時給定兩個串的長度n和m,請返回最長公共子序列的長度。保證兩串長度均小於等於300。
測試樣例:"1A2C3D4B56",10,"B1D23CA45B6A",12
返回:6
假設有字符串x和y, 這裏咱們用x[0,i]來表示x中下標爲[0,i]的子串切片,對應於python中的x[0:i+1]
。好比x="abcdea",那麼x[0,1]就表示"ab"。
定義f(i,j)表示x[0,i]和y[0,j]之間的最長公共子序列。
毫無疑問咱們又要想辦法用f(i-1,j-1)來表示f(i,j)。
當x[i]==y[j]時,f(i,j) = f(i-1,j-1) +1
當x[i]!=y[j]時,f(i,j) = max( f(i-1,j) , f(i,j-1) )
一樣的爲保證f()參數合法,當參數小於0時,返回值爲0。
當i<0或者j<0時,f(i,j)=0 //遞歸出口
這不難理解,好比f(0,0)實際上比較的是x[0]和y[0]兩個字符
的最長公共子序列,無非是0或者1。
而f(-1,2)中參數-1表示x取一個空串,y取y[0,2]。空串和任何字符串的LCS毫無疑問都是0
以x="abcdea",y="aebcda"爲例,構造dp表格以下:
class LCS { public: int findLCS(string A, int n, string B, int m) { vector<vector<int> > dp(n,vector<int>(m,0)); for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { if(A[i]==B[j]){ int t=0; if(i-1>=0 && j-1>=0) t=dp[i-1][j-1]; dp[i][j]=t+1; } else{ int t1=0; int t2=0; if(i-1>=0) t1=dp[i-1][j]; if(j-1>=0) t2=dp[i][j-1]; dp[i][j]=max(t1,t2); } } } //右下角 return dp[n-1][m-1]; } };
這裏須要考慮的邊界狀況較多,爲簡化代碼,能夠考慮字符串從1開始數
dp[i][j]就表示x[0,i-1]和y[0,j-1]的最長公共子序列
對應遞歸公式[1]:
這樣的dp矩陣,字符串的下標和矩陣的行號列號會有些錯位:
不須要判斷越界問題,代碼精簡,可是很差理解
class LCS { public: int findLCS(string A, int n, string B, int m) { //(n+1)*(m+1) vector<vector<int> > dp(n+1,vector<int>(m+1,0)); //按照dp的index來填充矩陣 for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(A[i-1]==B[j-1]) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } return dp[n][m]; } }; //或者 class LCS { public: int findLCS(string A, int n, string B, int m) { vector<vector<int> > dp(n+1,vector<int>(m+1,0)); //按照string的索引來填充dp矩陣 for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { if(A[i]==B[j]) dp[i+1][j+1]=dp[i][j]+1; else dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]); } } return dp[n][m]; } };
問題定義[2]
話說有一哥們去森林裏玩發現了一堆寶石,他數了數,一共有n個。 但他身上能裝寶石的就只有一個揹包,揹包的容量爲C。這哥們把n個寶石排成一排並編上號: 0,1,2,…,n-1。第i個寶石對應的價值和重量分別爲V[i]和W[i] 。排好後這哥們開始思考: 揹包總共也就只能裝下體積爲C的東西,那我要裝下哪些寶石才能讓我得到最大的利益呢?
揹包問題分爲01揹包問題和部分揹包問題,區別在於物品是否不可分割。
這裏的物品不能分割,討論的是01揹包問題。
假設有1,2,3...n一共n個物品。其中v[i]表示第i個物品價值。w[i]表示第i個物品重量。
d(i,j)表示,要把前i個物品放入揹包,揹包容量爲j
1 遞歸公式:d(i,j)=
2 列表計算
佔位
#include<iostream> #include <stdio.h> #include<vector> //#include"fsjtools.h" using namespace std; int main() { int n,c;//number,cap while(cin>>n>>c) { vector<int> w(n+1,0);//weight vector<int> v(n+1,0);//value for(int i=1;i<=n;i++) cin>>w[i]>>v[i];//start from 1 //printVector(w); //printVector(v); vector<vector<int> > dp(n+1,vector<int>(c+1,0));//初始化爲0 for(int i=1;i<=n;i++) { for(int j=0;j<=c;j++) { if(j>=w[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]] + v[i]); else dp[i][j]=dp[i-1][j]; } } cout<<dp[n][c]<<endl; } }
樣例輸入 5 10 4 9 3 6 5 1 2 4 5 1 4 9 4 20 3 6 4 20 2 4 5 10 2 6 2 3 6 5 5 4 4 6 樣例輸出 19 40 15