LYY ~ MY ... So — What ?
N個字符串
理論知識:
代碼實現:
#include <iostream>
#include <string>
#include <ctime>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
// 判斷字符串subSeq中的每一個字符在字符串str中的出現順序是否爲遞增
bool isSubsequence(const string& str, const string& subSeq) {
int index = -1; //設爲-1而不是0,因爲後邊還要 +1;
for (char ch : subSeq) {
index = str.find_first_of(ch, index + 1); // 起始位置得是下一位,故需要 +1;
if (index == str.npos) {
return false;
}
}
return true;
}
// 多串的公共子序列判斷;
bool isComSeq(const vector<string>& vs, const string& subseq) {
for (string str : vs) {
if (!isSubsequence(str, subseq)) {
return false;
}
}
return true;
}
// 2 ^ 64 是上限,故 只能處理母串長度 <64 的情況;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int n; // 帶輸入的字符串個數;
vector<string> vs; // 待輸入的字符串;
string s; // 臨時存儲輸入的字符串;
string s_little; // s輸入的最短的字符串;
while (cin >> n) {
while (n--) {
cin >> s;
vs.push_back(s); // 存入字符串;
if (s_little.length() < s.length()) {
s_little = s;
}
}
clock_t startTime = clock();
// 確保遍歷的是短字符串的所有子序列,並且是採用剔除元素的方式、自長到短遍歷
int len = s_little.length();
int cont = 1 << len;
string subSeq; // 臨時存儲子序列;
set<string> ss; // 存儲所有公共子序列;
int longest = 0; // 最長公共子序列長度;
bool flag = false; // 公共子序列是否存在;
// i 將會直接影響到選擇子集元素個數的多少(二進制表示);
for (int i = cont - 1; i >= 0; --i) {
for (int j = 0; j < len; ++j) {
if (i & (1 << j)) {
//cout << s1[j];
subSeq += s_little[j];
}
}
//cout << "subSeq = " << subSeq << endl;
// 接着判斷 s_little 的子序列 subSeq 是否也同時是 其他字符串 的子序列;
if (isComSeq(vs,subSeq)) {
if (longest <= subSeq.size()) { // 是公共子序列不行,還得是最長公共子序列才進行存儲;
ss.insert(subSeq);
longest = subSeq.size();
flag = true; // 不放在 if 語句外邊實爲減少不必要的賦值次數;
}
}
subSeq.clear(); // 記得重置爲空;
}
// 不存在公共序列則輸出空串;
//if (ss.empty()) {// 此判別條件存在漏洞,因爲兩個任意字符串至少存在 空串 作爲公共子序列,故需要立flag;
if (!flag) {
cout << endl;
}
else {
for (auto item : ss) {
/*cout << item << endl;*/ // 輸出所有公共子序列;
if (item.length() == longest) {
cout << item << endl;
//break; // 只需要輸出ACSLL最小的最長公共子序列時,break;
}
}
}
// 重置容器爲空;
s_little.clear();
vs.clear();
// ss.clear(); // ss 是局部聲明,清空沒有必要;
// longgest = 0; 同理;
cout << "總共耗時:" << double(clock() - startTime) / CLOCKS_PER_SEC << "s" << endl;
}
return 0;
}
測試樣例:
拓展:N個字符環,求最長公共子序列?
代碼實現:
#include <iostream>
#include <string>
#include <ctime>
#include <algorithm>
#include <set>
#include <vector>
#include <cassert>
using namespace std;
// 判斷字符串subSeq中的每一個字符在字符串str中的出現順序是否爲遞增
bool isSubsequence(const string& str, const string& subSeq) {
int index = -1; //設爲-1而不是0,因爲後邊還要 +1;
for (char ch : subSeq) {
index = str.find_first_of(ch, index + 1); // 起始位置得是下一位,故需要 +1;
if (index == str.npos) {
return false;
}
}
return true;
}
// 字符串轉字符環;
vector<string> stringToRing(const string& str) {
vector<string> vs;
for (int i = 1; i <= str.size(); ++i) {
string str_right(&str[i], str.size() - i);
// string s_right(str, i, str.size() - i); // 等效寫法 1 ;
// string s_right = str.substr(i, str.size() - i); // 等效寫法 2 ;
string str_left(str, 0, i);
string s = str_right + str_left;
// cout << s << endl;
vs.push_back(s);
}
// 一個字符環 包含 n個與源字符串等長的字符串(n爲源字符串的元素個數,即長度);
assert(vs.size() == str.length());
return vs;
}
// 多個字符環的公共子序列判斷;
bool isComSeq(const vector<string>& vs, const string& subseq) {
for (string str : vs) {
// 在當前字符環中是否存在該子序列,存在一次即可滿足條件;
bool flag = false;
for (string item : stringToRing(str)) {
if (isSubsequence(item, subseq)) {
flag = true; // 存在一次即可;
break;
}
}
// 當前環找不着該序列;
if (!flag) {
return false;
}
}
return true;
}
// 2 ^ 64 是上限,故 只能處理母串長度 <64 的情況;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int n; // 帶輸入的字符串個數;
vector<string> vs; // 待輸入的字符串(源字符串);
string s; // 臨時存儲輸入的字符串;
string s_little; // s輸入的最短的字符串;
while (cin >> n) {
while (n--) {
cin >> s;
vs.push_back(s); // 存入字符環;
if (s_little.length() < s.length()) {
s_little = s;
}
}
clock_t startTime = clock();
string subSeq; // 臨時存儲子序列;
set<string> ss; // 存儲所有公共子序列;
int longest = 0; // 最長公共子序列長度;
bool flag = false; // 公共子序列是否存在;
for (string item : stringToRing(s_little)) {
// 確保遍歷的是短字符串的所有子序列,並且是採用剔除元素的方式、自長到短遍歷
int len = item.length();
int cont = 1 << len;
// i 將會直接影響到選擇子集元素個數的多少(二進制表示);
for (int i = cont - 1; i >= 0; --i) {
for (int j = 0; j < len; ++j) {
if (i & (1 << j)) {
//cout << s1[j];
subSeq += item[j];
}
}
//cout << "subSeq = " << subSeq << endl;
// 接着判斷 s_little 的子序列 subSeq 是否也同時是 其他字符串 的子序列;
if (isComSeq(vs, subSeq)) {
if (longest <= subSeq.size()) { // 是公共子序列不行,還得是最長公共子序列才進行存儲;
ss.insert(subSeq);
longest = subSeq.size();
flag = true; // 不放在 if 語句外邊實爲減少不必要的賦值次數;
}
}
subSeq.clear(); // 記得重置爲空;
}
}
// 不存在公共序列則輸出空串;
//if (ss.empty()) {// 此判別條件存在漏洞,因爲兩個任意字符串至少存在 空串 作爲公共子序列,故需要立flag;
if (!flag) {
cout << endl;
}
else {
for (auto item : ss) {
/*cout << item << endl;*/ // 輸出所有公共子序列;
if (item.length() == longest) {
cout << item << endl;
//break; // 只需要輸出ACSLL最小的最長公共子序列時,break;
}
}
}
// 重置容器爲空;
s_little.clear();
vs.clear();
ss.clear(); // ss 是局部聲明,清空沒有必要;
longest = 0; // 同理;
cout << "總共耗時:" << double(clock() - startTime) / CLOCKS_PER_SEC << "s" << endl;
}
return 0;
}
測試樣例:
題目補充:
題目來源:
ACM-ICPC 2018 北京賽區網絡預賽 Tomb Raider
特別說明:
該考題的輸出僅僅是一個ACSLL碼最小的最長公共子序列串,
自己的測試是左右公共子序列。
有上述 從N個字符串找最長公共子序列,到N個字符環找最長公共子序列的過程可以發現:環的公共子序列的結果數量明細增多。
並且值得注意的是:
代碼證容易被遺漏的地方是, s_little也需要先生成對應的字符環(多個字符串),再各自去生成各自的所有子序列,然後拿來校驗。
而不是僅僅針對 s_little 的源字符串生成所有子序列。
其他說明:
博客補充以強化理解:
助你深刻理解——最長公共子串、最長公共子序列(應該是全網數一數二的比較全面的總結了)
轉載請註明出處:
https://blog.csdn.net/I_love_you_dandan/article/details/103227017
以上代碼皆爲本人親自碼字、親自測試,如有問題需要諮詢或者指正,可以直接在評論區留言或者發送私信進行友好交流。
聯繫方式:[email protected]
2019/11/24 20:43