第一版(有思路无实现)
#include <iostream>
#include <string>
using namespace std;
// 写一个函数来判断a是不是b的子串
bool isson(string &a, string &b){
}
int main() {
// 获得输入
string s, t;
getline(cin, s);
getline(cin, t);
// 一种特殊情况:较短串即最长公共子串,可以从这个假设入手。
if(s.size() <= t.size()){
if (isson(s, t)) {
cout << s;
}else {
// 如果较短串不是,那么优先在比较短串少一个字符的串里找,这样可以保证最长。
// 分割字符串的时候只允许掐头去尾。
// 一旦找到公共子串立即停止。
// 为保证输出较短串中最先出现的子串,找子串时有优先掐尾,然后掐头。
}
}else {
if (isson(t, s)) {
cout << t;
}else {
// 同上。
}
}
}
// 64 位输出请用 printf("%lld")
// 不需要找到全部公共子串,然后提取符合要求的最长公共子串,保证自己要找的最先出现即可。
- 笔者思路:
- 写一个判断a是否为b的子串的函数,便于调用。
- 从较短串入手,如果较短串是较长串的子串,则较短串就是最终结果,否则,长度减一继续判断。
- 长度减一分为掐头去尾两种情况,这里要优先去尾。这样会让先出现的子串成为最终结果。
- 先找全部公共子串的思路是麻烦的,但是暴力枚举在无奈时也可以作为备选方案。
- 智能反馈:
- 使用字符串的 find() 方法,可以检查某个串是否出现在另一个字符串中。
- 先检查较短串本身是否就是公共子串,再从较短串的最长子串(长度-1)开始检查,一步步减少长度。
- 对于每个长度,从左到右检查所有可能的子串,一旦找到这个长度下有公共子串,就记录并停止搜索该长度并结束。
- 通过上述两步,可以确保找到最长公共子串后会立即停止搜索。
- 笔者疑问:
- 写循环可以从字符串的下脚标入手,利用substr(s.begin(), s.end() - i)去尾,利用substr(s.begin() + i, s.end())掐头。
- 设较短串长度为n,假如长度为n-2,要分三种情况讨论,假如长度为n-3,要分4种情况讨论,那么如何用数学语言表示或者用库函数解决问题呢?
- 智能解答:
- C++ 的 string::substr(pos, len) 可以方便地提取子串,我们可以用双重循环遍历所有可能的子串。
- 外层循环:遍历所有可能的子串长度 k(从 n 递减到 1)
- 内层循环:遍历所有起始位置 i(从 0 到 n - k)检查子串是否存在:用 longer.find(substr) != string::npos 判断是否在较长串中出现
第二版(编译错误)
#include <iostream>
#include <string>
using namespace std;
// 写一个函数来判断a是不是b的子串
bool isSubstring(string &a, string &b){
return b.find(a) != string::npos;
}
int main() {
// 获得输入
string s, t;
getline(cin, s);
getline(cin, t);
string shorter, longer;
if(s.size() <= t.size()){
shorter = s;
longer = t;
}else {
shorter = t;
longer = s;
}
for (int k = shorter.size(); k >= 1; k++) {
for(int i = 0; i <= shorter.size() - k; i++){
if (isSubstring(shorter.substr(i, k), longer)) {
cout << shorter.substr(i, k);
return 0; // 一旦找到直接返回
}
}
}
}
// 64 位输出请用 printf("%lld")
- 编译有两个地方错误——
- 函数参数类型不匹配:笔者定义的函数要传入左值(已命名的变量),而调用的时候传入了临时对象(右值)。将函数参数改为 const 引用可以解决问题。
- 符号比较警告:shorter.size() 返回 size_t,是无符号类型。应统一类型解决问题。
第三版(0/20,运行超时)
#include <iostream>
#include <string>
using namespace std;
// 写一个函数来判断a是不是b的子串
bool isSubstring(const string &a, const string &b){
return b.find(a) != string::npos;
}
int main() {
// 获得输入
string s, t;
getline(cin, s);
getline(cin, t);
string shorter, longer;
if(s.size() <= t.size()){
shorter = s;
longer = t;
}else {
shorter = t;
longer = s;
}
for (int k = shorter.size(); k >= 1; k++) {
for(int i = 0; i <= (int)shorter.size() - k; i++){
if (isSubstring(shorter.substr(i, k), longer)) {
cout << shorter.substr(i, k);
return 0; // 一旦找到直接返回
}
}
}
return 0;
}
// 64 位输出请用 printf("%lld")
- 运行超时:程序未能在规定时间内运行结束,应检查是否循环有错或算法复杂度过大。
- 问题在于k是从大变小的,所以应该是k--而不是k++。
第四版(AC)
#include <iostream>
#include <string>
using namespace std;
// 写一个函数来判断a是不是b的子串
bool isSubstring(const string &a, const string &b){
return b.find(a) != string::npos;
}
int main() {
// 获得输入
string s, t;
getline(cin, s);
getline(cin, t);
string shorter, longer;
if(s.size() <= t.size()){
shorter = s;
longer = t;
}else {
shorter = t;
longer = s;
}
for (int k = shorter.size(); k >= 1; k--) {
for(int i = 0; i <= (int)shorter.size() - k; i++){
if (isSubstring(shorter.substr(i, k), longer)) {
cout << shorter.substr(i, k);
return 0; // 一旦找到直接返回
}
}
}
return 0;
}
// 64 位输出请用 printf("%lld")
- 做这道题有两个关键的参数i和k。如果把确定子串的矩形比喻成一个窗口,那么这两个参数分别是窗口的起始位置和横向长度。
- i负责移动窗口位置,k负责确定窗口长度。
- 其实,
substr()
这个函数也是通过这两个参数分割子串的。 bool isSubstring(const string &a, const string &b)
函数中的参数是常引用。- 既可以接受左值,也可以接受右值。
- 既有引用能避免拷贝字符串以提高性能,又有const能避免字符串在函数内被修改以保证安全。
- 常引用是处理只读字符串参数的推荐方式,尤其适合子串判断这类不修改输入的场景。