Common Subsequence(DP求LCS)

题意:求两字符串最长公共子序列长度

先假设字符串起点为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);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值