虽然最长公共子序列和背包算是DP中最简单的问题了,但是对研究一些东西还是挺有用处的
为了整理DP,现整理如下:(含滚动数组)
1)最长公共子序列的长度的动态规划方程
设有字符串a[0...n],b[0...m],下面就是递推公式。字符串a对应的是二维数组num的行,字符串b对应的是二维数组num的列。
另外,采用二维数组flag来记录下标i和j的走向。数字"1"表示,斜向下;数字"2"表示,水平向右;数字"3"表示,竖直向下。这样便于以后的求解最长公共子序列。
#include <bits/stdC++.h> using namespace std; char a[500],b[500]; char num[501][501]; ///记录中间结果的数组 char flag[501][501]; ///标记数组,用于标识下标的走向,构造出公共子序列 void LCS()///动态规划求解 { for(int i=1;i<=strlen(a);i++) for(int j=1;j<=strlen(b);j++) { if(a[i-1]==b[j-1]) ///注意这里的下标是i-1与j-1 num[i][j]=num[i-1][j-1]+1,flag[i][j]=1; ///斜向下标记 else if(num[i][j-1]>num[i-1][j]) num[i][j]=num[i][j-1], flag[i][j]=2; ///向右标记 else num[i][j]=num[i-1][j], flag[i][j]=3; ///向下标记 } } void getLCS() ///采用倒推方式求最长公共子序列 { char res[500],pnt=0; ///pnt用于保存结果的数组标志位 for(int i=strlen(a),j=strlen(b); i>0&&j>0 ; ) { if(flag[i][j]==1) res[pnt++]=a[i-1], ,i--,j--; ///如果是斜向下标记 else if(flag[i][j]==2) j--; ///如果是斜向右标记 else if(flag[i][j]==3) i--; ///如果是斜向下标记 } for(i=pnt-1;i>=0;i--) printf("%c",res[i]); } int main() { int i; strcpy(a,"ABCBDAB"); strcpy(b,"BDCABA"); memset(num,0,sizeof(num)); memset(flag,0,sizeof(flag)); LCS(); printf("%d\n",num[strlen(a)][strlen(b)]); getLCS(); return 0; }
滚动数组,简洁很多了
滚动数组把n*m的二维数组压缩成 2*m的数组,只需要用到now层和pre层的数据(这层和上一层),通过now和pre的不断交换实现n*m的数组功能,大大优化了空间复杂度。
还有一维就可以实现的办法,不过代码很难理解,个人觉得没有那个必要(代码优美才是王道)。
#include <bits/stdc++.h> using namespace std; int main() { int i,j,dp[2][10086],t; char a[10086],b[10086]; bool now,pre; scanf("%d",&t); while(t--) { scanf("%s%s",a,b); memset(dp,0,sizeof(dp)); int lena=strlen(a),lenb=strlen(b); for(now=1,pre=0,i=0; i<lena; i++) for(swap(now,pre),j=0; j<lenb; j++) if(a[i]==b[j]) dp[now][j+1]=dp[pre][j]+1; else dp[now][j+1]=dp[pre][j+1]>dp[now][j]?dp[pre][j+1]:dp[now][j]; printf("%d\n",dp[now][lenb]); } return 0; }