【蓝桥杯真题详解】蓝肽子序列 --- 二维动态规划与字符串哈希

写在前面

Hello大家好我是秋刀鱼,今天给大家带来的是蓝桥杯真题 蓝肽子序列 的解题思路讲解。

这道题目是一道基础题目的变式,我将从基础题目开始分析逐步带你理解如何计算字符串最长公共子序列与处理该题的变式部分。


往期蓝桥杯真题解析

【蓝桥杯真题训练 day14】今日四道真题全解析

【蓝桥杯冲刺 day12】题目全解析

【蓝桥杯冲刺 day10】题目全解析 — 难题突破

【蓝桥杯冲刺 day8】题目全解析 —附上LeetCode 每日一题

【蓝桥杯冲刺 day7】 题目全解析 — 附上LeetCode周赛 银联-03. 理财产品

【蓝桥杯冲刺 day4】题目全解析 — 每日刷题解析


蓝肽子序列

题目传送门🚀

题目描述

L 星球上的生物由蛋蓝质组成,每一种蛋蓝质由一类称为蓝肽的物资首尾连接成一条长链后折叠而成。

生物学家小乔正在研究 L 星球上的蛋蓝质。她拿到两个蛋蓝质的蓝肽序列,想通过这两条蓝肽序列的共同特点来分析两种蛋蓝质的相似性。

具体的,一个蓝肽可以使用 1 至 5 个英文字母表示,其中第一个字母大写,后面的字母小写。一个蛋蓝质的蓝肽序列可以用蓝肽的表示顺序拼接而成。

在一条蓝肽序列中,如果选取其中的一些位置,把这些位置的蓝肽取出,并按照它们在原序列中的位置摆放,则称为这条蓝肽的一个子序列。蓝肽的子序列不一定在原序列中是连续的,中间可能间隔着一些未被取出的蓝肽。

如果第一条蓝肽序列可以取出一个子序列与第二条蓝肽序列中取出的某个子序列相等,则称为一个公共蓝肽子序列。

给定两条蓝肽序列,找出他们最长的那个公共蓝肽子序列的长度。

输入描述

输入两行,每行包含一个字符串,表示一个蓝肽序列。字符串中间没有空格等分隔字符。

其中有 ,两个字符串的长度均不超过 1000。

输出描述

输出一个整数,表示最长的那个公共蓝肽子序列的长度。

输入输出样例

示例

输入

LanQiaoBei
LanTaiXiaoQiao

输出

2

解题思路

这道题看着十分唬人,但实际上就是最长公共子序列题目的一种变式题。既然是最长公共子序列题目变式题,那我们就从这道基础题开始讲起:

LeetCode 原型题目讲解

题目传送门

求两个串的最长公共子序列,使用的方法是**动态规划**:

1、状态的定义

定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 存储 s 1 s1 s1 的前 i i i 个字符与 s 2 s2 s2 的前 j j j 个字符,形成的最大公共子序列长度。

2、状态转义方程

状态定义后开始编写状态转移方程:

  • s 1 [ i ] = = s 2 [ j ] s1[i] == s2[j] s1[i]==s2[j] d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i1][j1]+1

​ 该情况说明了最后一位的字符是相同的,所以公共子序列的长度加一。举个🌰: 对于 s1 = “ab” ,s2 = “cb” ,那么其最长公共子序列的值为 ab 的最长公共子序列长度 0 + 1 = 1

  • s 1 [ i ] ! = s 2 [ j ] s1[i] !=s2[j] s1[i]!=s2[j] d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])

​ 该情况说明两串的最后一位不相等,那么此时 d p [ i ] [ j ] dp[i][j] dp[i][j] 应该是 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] 的最大值。还是举个🌰 :对于 s1=“abc” ,s2 = “bd” ,那么其最长公共子序列的值为 “ab"与"bd” 和 “abc"与"b” 的公共子序列长度: 1

总结下来就是:

3、状态的初始化

对于 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0] 也就是两个空串的最大公共子序列长度,自然为0,所以 d p [ 0 ] [ 0 ] = 0 dp[0][0]=0 dp[0][0]=0

LeetCode 例题 AC代码:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        const int M = text1.size();
        const int N = text2.size();
        vector<vector<int>> dp(M + 1, vector<int>(N + 1, 0));
        for (int i = 1; i <= M; ++i) {
            for (int j = 1; j <= N; ++j) {
                if (text1[i - 1] == text2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[M][N];
    }
};
开始解题

有了前面的思路我们再来看看蓝桥杯的这道题:

蓝桥杯题目中唯一的变更是:原型题目找的是字符的最长子序列,而蓝桥杯中找的是蓝肽的最长子序列

因为蓝钛是一个字符串,剔除的不再是一个字符而是一个串,操作时很不方便。既然是一个字符串,且剔除时是整个蓝钛被剔除,那么我能不能将字符串转为一个整形数保存呢?如果可以的话,是不是就能完全参照最长子序列的解法解决了呢。

这里我推荐使用的是字符串哈希算法,能够将字符串转换为唯一的一个整形数。如果不太了解的朋友可以看看我之前的博客【字符串哈希】讲解与例题

为了区分不同的字符串,可以定义一个足够大的质数,通过该质数与字符串的每一位相乘再相加,获得一个散列值。这个散列值能确保:不相等的字符串值一定不同,相同的字符串值一定相同。下面是获得字符串散列值的代码:

#define ll long long
...
ll P = 131313;
int getHash(string str) {
	ll hash = 0;
	for (char c : str) {
		hash += (long)c - 'a' + 1;
		hash *= P;
	}
	return hash;
}

定义函数build就将一个字符串中的所有蓝肽的散列值求出,并按先后顺序放入数组中:

// 将字符串中的蓝肽转为散列值存储vector中
void build(vector<ll>& vec, string &str) {
	int len = str.size();
	int l, r;
	l = r = 0;
	while (r != len) {
		++r;
		if (str[r] <='Z'&&str[r]>='A') {
			vec.push_back(getHash(str.substr(l, r-l)));
			l = r;
		}
	}
    // 处理最后一次添加的情况
	if (r != l) {
		vec.push_back(getHash(str.substr(l, r - l)));
	}
}

现在我们将一长串的蓝肽序列,转换为一个个散列值,每一个散列值可以看做最长子序列题目中的一个字符,现在依照模板就可以完成所有代码:

AC代码
#include <iostream>
#include <vector>
#define M 1010
#define ll long long
using namespace std;
// 定义质数
ll P = 131313;
vector<ll>str1_vc, str2_vc;
string str1, str2;
int dp[M][M] = { {0} };

// 获取散列值
int getHash(string str) {
	ll hash = 0;
	for (char c : str) {
		hash += (long)c - 'a' + 1;
		hash *= P;
	}
	return hash;
}

// 将字符串中的蓝肽转为散列值存储vector中
void build(vector<ll>& vec, string &str) {
	int len = str.size();
	int l, r;
	l = r = 0;
	while (r != len) {
		++r;
		if (str[r] <='Z'&&str[r]>='A') {
			vec.push_back(getHash(str.substr(l, r-l)));
			l = r;
		}
	}
	if (r != l) {
		vec.push_back(getHash(str.substr(l, r - l)));
	}
}
int main()
{
	cin >> str1;
	cin >> str2;
	build(str1_vc, str1);
	build(str2_vc, str2);
	
	int len1, len2;
	len1 = str1_vc.size(); len2 = str2_vc.size();
  
	for (int i = 1; i <= len1; ++i) {
		for (int j = 1; j <= len2; ++j) {
			if (str1_vc[i - 1] == str2_vc[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[len1][len2];
	return 0;
}

写在最后

代码、论述中有任何问题,欢迎大家指出,同时如果有任何疑问,也能够在评论区中留言,大家共同讨论共同进步!

如果觉得博主写的不错的话,可以点赞支持一下

img

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋刀鱼与猫_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值