什么是子序列
定义还是要搞清楚的
字符串s1s2···sn的子序列可以是si1si2···sim(这里的i1
,i2
,im
并不一定要规律递增,只要保证是递增即可,例如2,4,6,9
,或1,2,3,5
皆可)。区分一个概念,字串是需要连续的,子序列只要保证下标递增即可。
思路
不用说,隔着屏幕都能嗅到动态规划的味道。
首先确定转移方程,我们用dp[m][n]
这个二维数组存储每一个状态下的最大子序列的长度。
dp[i][j]
的定义:字符串s1 ~ si处和字符串t1 ~ tj状态下的最大子序列的长度。 这句话最重要,dp[i][j]
的定义不清晰,后面代码写起来很容易搞混。
注意,这里的i
和j
可能有点搞。因为一般我们字符串的下标是从0
开始的,而这里我们规定从1开始(一开始别那么绕嘛)。
接着dp[i][j]
会怎么转移呢?
我们分两种情况讨论
- si==ti
这种情况直接dp[i][j]=dp[i-1][j-1]+1
即可,因为dp[i-1][j-1]
存的就是上一种状态的最大值,因此直接+1即可 - si!=tj
两个字符不相等,说明当前情况的最长子序列的长度依然维持上一种情况的最大值,所以我们要向之前的结果看,能到达这中情况的一共有两种老情况,分别是i
不变,j
向后退一步,和j
不变,i
向后退一步,所以表达式为
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
完整表达式为
if(s[i]==t[j]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
外层再套两个for循环即可。
AC代码
//像现在时间紧课业重的情况,我们不奢求去弄清楚每一种算法细微的区别
//我们的定位应该是找到一种能解决问题的方法,然后弄清楚这种方法
//LCS
#include<iostream>
#include<cstring>
using namespace std;
int m,n;
const int MAX_SIZE=1010;
char s[MAX_SIZE],t[MAX_SIZE];
int dp[MAX_SIZE][MAX_SIZE];
void solve();
int main(){
scanf("%d %d\n",&m,&n);
for(int i=1;i<=m;i++){
scanf("%c",&s[i]);
}
getchar();
for(int i=1;i<=n;i++){
scanf("%c",&t[i]);
}
solve();
return 0;
}
void solve(){
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(s[i]==t[j]){//当前字符相等,直接就是dp[][]+1,因为ij在字符串中和dp中表示的意义相同
dp[i][j]=dp[i-1][j-1]+1;
}else{//字符串不同的情况下
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);//分别向前看即可
}
}
}
printf("%d",dp[m][n]);
}
字符串从0开始存储
//TODO:dp[i][j]定义为s0~si-1和t0~t-1的最长子序列的长度
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
const int MAX_SIZE=1010;
int m,n;
string s,t;
int dp[MAX_SIZE][MAX_SIZE];
void solve();
int main(){
cin >> m >> n;
cin >> s >> t;
solve();
return 0;
}
//现在是字符串从0开始存储
void solve(){
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(s[i-1]==t[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
cout << dp[m][n];
}