题意:求两字符串最长公共子序列长度
先假设字符串起点为1,好讲些。dp[i][j]表示字符串1的前i字符和字符串2的前j个字符的最长公共子序列长度,下面我们来推到一下状态转移方程
if (arr1[i] == arr2[j]) dp[i][j] = dp[(i - 1)][(j - 1)] + 1;
为什么是由dp[i-1][j-1]转移,而不是dp[i-1][j]或dp[i][j-1]呢?因为如果arr[i]==arr[j]时,必定有dp[i][j]>da[i-1][j-1],但不一定有dp[i][j]>dp[i][j-1]和dp[i][j]>dp[i-1][j],因为str1[i]可能会等于str[j-1],当arr[j]==arr[i]时最长公共子序列长度不一定会增加,因一种情况同理。
如果arr[i]!=arr[j],那么dp[i][j]可以由dp[i-1][j]和dp[i][j-1]转移而来。
未优化代码
if (str1[i] == str2[j]) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
不过这题需要进行空间优化(MLE过了,所以不爱了)
我们可以发现对dp[i][x]更新时只需要知道dp[i-1][x]和dp[i][y],所以我们可以对第一维的i对二取模,节省空间
AC代码
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
const int N = 2020;
int dp[5][N];
int main(){
ios::sync_with_stdio(false);
string str1, str2;
while (cin >> str1 >> str2) {
memset(dp, 0, sizeof(dp));
int len1 = str1.length() - 1, len2 = str2.length() - 1;
for (int i = len1; i >=0; i--) {
for (int j = len2; j >= 0; j--) {
if (str1[i] == str2[j]) dp[i%2][j] = dp[(i + 1)%2][j+1] + 1;
else {
dp[i%2][j] = max(dp[(i + 1)%2][j], dp[i%2][j + 1]);
}
}
}
cout << dp[0][0] << endl;
}
}
还有对时间的优化方法,使时间复杂度降低到O(n*logn);
简单说一下核心思想:找到str2各个字符在str1上的位置,求一个最长上升子序列,这个序列的长度即为我们所要求的最长公共子序列的长度
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int N = 1e5+10;
int arr[N];
int ans[N];
int len;
map<int, int> mp;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int t;
scanf("%d", &t);
mp[t] = i;
}
for (int i = 1; i <= n; i++) {
int t;
scanf("%d", &t);
arr[i] = mp[t];
}
ans[++len] = arr[1];
for (int i = 2; i <= n; i++) {
if (arr[i] > ans[len]) {
ans[++len] = arr[i];
}
else {
int l = 0, r = len;
while (l <= r) {
int mid = (l + r) >> 1;
if (ans[mid] > arr[i]) {
r = mid-1;
}
else {
l = mid + 1;
}
}
ans[l] = min(ans[l], arr[i]);
}
}
printf("%d\n", len);
}