最长公共子序列(LCS)及其应用(密码脱落)

最长公共子序列(LCS)及其应用(密码脱落)

给定两个 string 字符串,找出他们的最长公共子序列(Longest Common Sequence,LCS)。

例如 string A = “BDCABA“,string B = ”ABCBDAB“,他们的LCS = 4。
在这里插入图片描述
求最长公共子序列的经典方法属于一种动态规划(Dynamic Programming,DP)。

而动态规划的两个基本要素为:① 最优子结构 ② 重叠子问题

① 最优子结构

设 X = x1 x2xn Y = y1 y2ym

若 xn == ym,说明该元素一定存在于LCS中,

故只要求 LCS(Xn-1,ym-1,这是一个最优的子问题。

若 xn != ym,则继续分类缩小问题的范围。
在这里插入图片描述
故只要求 max{LCS(Xn-1,Ym),LCS(Xn,Ym-1)} 即可。

② 重复子问题

LCS(X,Y):LCS(Xn-1,Ym-1LCS(Xn-1,Ym,LCS(Xn,Ym-1

LCS(Xn-1,Ym:LCS(Xn-2,Ym-1),LCS(Xn-2,Ym),LCS(Xn-1,Ym-1

……

如图会有很多重复的子问题,若用递归做时间会以指数增长。

用 dp 做的直接进行“查表”即可。

综上
在这里插入图片描述
参考代码:

int LCS(string str1, string str2){
	// 初始化第一行和第一列 
	for(int i = 0; i <= len1; i++){
		dp[i][0] = 0;
	}
	for(int i = 0; i <= len2; i++){
		dp[0][i] = 0;
	}
	// 构建dp数组 
	for(int i = 1; i <= len1; i++){
		for(int j = 1; j <= len2; j++){
			if(str1[i] == str2[j]){  // 如果该位置上的字符相同 dp值 = 没有当前字符时的LCS值+1 
				dp[i][j] = dp[i-1][j-1]+1;
			}
			else{  // 如果不同 dp值 = 单字符串去掉当前字符时的两个LCS中的最大值 
				dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
			}
		}
	}
	return dp[len1][len2];
}
关于LCS的应用——密码脱落:

问题描述如下

X星球的考古学家发现了一批古代留下来的密码。
这些密码是由A、B、C、D 四种植物的种子串成的序列。
仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串)。
由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。

你的任务是:
给定一个现在看到的密码串,计算一下从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。

输入一行,表示现在看到的密码串(长度不大于1000)
要求输出一个正整数,表示至少脱落了多少个种子。

例如,输入:
ABCBA
则程序应该输出:
0

再例如,输入:
ABECDCBABC
则程序应该输出:
3

也就是说至少需要添加多少个字符才可以变成一个镜像串。

怎么联系到最长公共子序列呢?

因为原本是一个镜像串,镜像串的特点就是正序和逆序相同。

我们可以利用这一点,把脱落后的密码串进行反转,再跟反转前的密码串进行比对。

找到最长的公共子序列之后,其余剩下没有匹配成功的字符就是需要添加的字符。

这样就可以保证添加最少的字符来还原一个镜像串。

参考代码如下:

// 第七届蓝桥杯第九题 
#include<iostream>
using namespace std;

int **arr;

// 翻转 
string reverse(string str){
	string sub = str;
	for(int i = 0; i < sub.length()/2; i++){
		char ch = sub[i];
		sub[i] = sub[sub.length()-1-i];
		sub[sub.length()-1-i] = ch; 
	}
	return sub;
}

// 求最长公共子序列 
int getLCS(string str1, string str2){
	// 动态创建二维数组 
	arr = new int*[str1.length()+1];
	for(int i = 0; i <= str1.length(); i++){
		arr[i] = new int[str2.length()+1];
	}
	// 将第一行和第一列初始化为0 
	for(int i = 0; i <= str1.length(); i++){
		arr[i][0] = 0;
	}
	for(int i = 0; i <= str2.length(); i++){
		arr[0][i] = 0;
	}
	// 根据公式计算二维数组 
	for(int i = 1; i <= str1.length(); i++){
		for(int j = 1; j <= str2.length(); j++){
			if(str1[i] == str2[j]){
				arr[i][j] = arr[i-1][j-1]+1;
			}
			else if(arr[i-1][j] > arr[i][j-1]){
				arr[i][j] = arr[i-1][j];
			}
			else{
				arr[i][j] = arr[i][j-1];
			}
		}
	}
	// 返回两个字符串的LCS 
	return arr[str1.length()][str2.length()];
}

int main(){
	string str;
	while(cin >> str){
		string sub = reverse(str);
		int len = getLCS(str, sub);
		// 用原长减去LCS就是答案 
		cout << str.length()-len << endl; 
		for(int i = 0; i < str.length()+1; i++){
			delete []arr[i];
		}
		delete []arr;
	}
	return 0;
}

【END】感谢观看

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值