Acwin-3692. 最长连续公共子序列——待续
1.题目
输入两个字符串 s1,s2
。
输出最长连续公共子串长度和最长连续公共子串。
输入格式
一行,两个字符串 s1,s2,用空格隔开。
输出格式
第一行输出最长连续公共子串的长度
第二行输出最长连续公共子串。如果不唯一,则输出 s1
中的最后一个。
数据范围
1≤|s1|,|s2|≤100
数据保证至少存在一个连续公共子串。
输入样例:
abcdefg qwercdefiok
解释
输出样例:
4
cdef
2.题解
2.1.1 暴力枚举
思路
注意strcmp用于比较char字符串(字符常量),strcmp中: 1. s1 > s2 返回1, 2. s1 == s2, 返回0, 3. s1 < s2,返回-1
比较两个字符串string类型,使用函数compare即可!
代码
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1, s2;
cin >> s1 >> s2;
int ans = 1;
string s;
int n = min(s1.length(), s2.length());
for(int i = 1; i <= n; i++){
bool found = false;
for(int j = 0; j <= s1.length() - i; j++){
for(int k = 0; k <= s2.length() - i; k++){
string ss1 = s1.substr(j, i), ss2 = s2.substr(k, i);
// if(ss1.compare(ss2) == 0){
if(ss1 == ss2){
ans = i;
s = ss1;
}
}
}
}
cout << ans << endl << s;
return 0;
}
复杂度
时间复杂度:\(O(n^4)\)
2.1.2 暴力枚举(优化)
思路
找的是最长的,最后一个出现的,所以倒过来枚举即可(从大到小 + 字符串逆序比较)
代码
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1, s2;
cin >> s1 >> s2;
int ans = 1;
string s;
int n = min(s1.length(), s2.length());
for(int i = n; i >= 1; i--){
for(int j = s1.length() - i; j >= 0; j--){
for(int k = s2.length() - i; k >= 0; k--){
string ss1 = s1.substr(j, i), ss2 = s2.substr(k, i);
if(ss1 == ss2){
ans = i;
s = ss1;
}
}
if(s.size()) break;
}
if(s.size()) break;
}
cout << ans << endl << s;
return 0;
}
2.2.1 二分查找
思路
这里使用左闭右开的二分查找方式,也就是 while(left < right){ ...left = mid + 1(满足条件) ... right = mid(不满足条件)}
这里每次更新搜索区间(不包含上次找到的target位置),每次满足条件都是向着长度更大的方向进行搜索
补充:如果想向着更小的方向寻找:
while(left < right){ ...right = mid - 1(满足条件) ... left = mid (不满足条件)}
代码
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1, s2;
cin >> s1 >> s2;
int ans = 1;
string s;
int n = min(s1.length(), s2.length());
int left = -1, right = n - 1;
while(left < right){
bool found = false;
int mid = (left + right) / 2;
int len = mid + 1; // 长度和下标不一样
// 下标-下标 等价于 长度 - 长度(下标 + 1 - 下标 - 1) ,所以这里不需要额外处理
for(int j = s1.length() - len; j >= 0 && !found; j--){
for(int k = s2.length() - len; k >= 0 && !found; k--){
string ss1 = s1.substr(j, len), ss2 = s2.substr(k, len);
if(ss1 == ss2){
ans = len;
s = ss1;
found = true;
}
}
}
if(found){
left = mid + 1;
}else{
right = mid;
}
}
cout << ans << endl << s;
return 0;
}
代码二——左闭右闭
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1, s2;
cin >> s1 >> s2;
int ans = 1;
string s;
int n = min(s1.length(), s2.length());
int left = 0, right = n; // 长度取值范围为 [0, n]
while(left <= right){
bool found = false;
int mid = (left + right) / 2;
int len = mid + 1; // 长度和下标不一样
// 下标-下标 等价于 长度 - 长度(下标 + 1 - 下标 - 1) ,所以这里不需要额外处理
for(int j = s1.length() - len; j >= 0 && !found; j--){
for(int k = s2.length() - len; k >= 0 && !found; k--){
string ss1 = s1.substr(j, len), ss2 = s2.substr(k, len);
if(ss1 == ss2){
ans = len;
s = ss1;
found = true;
}
}
}
if(found){
left = mid + 1;
}else{
right = mid - 1;
}
}
cout << ans << endl << s;
return 0;
}
复杂度
时间复杂度: \(O(nlog_n)\)
2.2.2 二分 + 字符串哈希(滚动哈希)
思路
代码
#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>
using namespace std;
class Solution {
public:
pair<int, string> longestCommonSubstring(string s1, string s2) {
int n1 = s1.length();
int n2 = s2.length();
int P = 131313; // 基数
long long MOD = 1e9 + 7; // 余数
// 计算 s1 和 s2 的哈希值和幂次方值
vector<long long> h1(n1 + 1, 0), p1(n1 + 1, 1);
vector<long long> h2(n2 + 1, 0), p2(n2 + 1, 1);
for (int i = 1; i <= n1; ++i) {
h1[i] = (h1[i - 1] * P + s1[i - 1]) % MOD;
p1[i] = (p1[i - 1] * P) % MOD;
}
for (int i = 1; i <= n2; ++i) {
h2[i] = (h2[i - 1] * P + s2[i - 1]) % MOD;
p2[i] = (p2[i - 1] * P) % MOD;
}
// 哈希函数
auto getHash = [&](const vector<long long> &h, const vector<long long> &p, int l, int r) {
return (h[r + 1] - h[l] * p[r - l + 1] % MOD + MOD) % MOD; // 将在h[r + 1]中 h[l] 多乘的 p[r - l + 1] 的次数补上(这里下标对应次数)
};
// 二分查找最长公共子串长度
int left = 0, right = min(n1, n2), maxLen = 0;
string longestSubstr = "";
// 左闭右闭形式
while (left <= right) {
int mid = left + (right - left) / 2;
unordered_map<long long, vector<int>> hashTable;
// 存储s1中所有长度为mid的子串的哈希值
for (int i = 0; i + mid - 1 < n1; ++i) {
long long hashValue = getHash(h1, p1, i, i + mid - 1);
hashTable[hashValue].push_back(i);
}
bool found = false;
// 倒序查找 s2 保证找到的是最后一个!
for (int i = n2 - mid; i >= 0 && !found; --i) {
long long hashValue = getHash(h2, p2, i, i + mid - 1);
if (hashTable.find(hashValue) != hashTable.end()) {
// 如果在s2中找到相同的哈希值
for (int pos : hashTable[hashValue]) {
if (s1.substr(pos, mid) == s2.substr(i, mid)) {
found = true;
maxLen = mid;
longestSubstr = s1.substr(pos, mid);
}
}
}
}
if (found) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return {maxLen, longestSubstr};
}
};
int main() {
string s1, s2;
cin >> s1 >> s2;
Solution solution;
pair<int, string> result = solution.longestCommonSubstring(s1, s2);
cout << result.first << endl;
cout << result.second << endl;
return 0;
}
复杂度
时间复杂度 \(O ((n1 +n2)log(min(n1,n2)))\), 即\(O(nlogn)\)
1.哈希预处理
计算哈希值和幂次方值的时间复杂度为O(n1 + n2)
2.二分查找
二分查找的次数为 O(log(min(n1,n2)))
3.子串哈希值计算和匹配:
在每次二分查找中,对于每个中间长度 mid,我们需要遍历 O(n1+n2) 个子串,并进行哈希值的查找和比较。
2.3 后缀数组 + LCP
思路
代码
复杂度
时间复杂度: \(O(nlogn)\)