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)\)

posted @ 2024-07-14 20:48  DawnTraveler  阅读(4)  评论(0编辑  收藏  举报