一、题目
1、题目描述
小蓝有很多齿轮,每个齿轮的凸起和凹陷分别用一个字符表示,一个字符串表示一个齿轮。
如果这两个齿轮的对应位分别是同一个字母的大小写,我们称这个两个齿轮是契合的。
例如:AbCDeFgh
和 aBcdEfGH
就是契合的,但是 abc
和 aBC
不是契合的。
这天,小蓝的弟弟小桥从抽屉里拿来了两个齿轮,小蓝想知道,这两个齿轮是不是契合的。
特别需要注意的是,齿轮是环形的,所以是可以旋转的(顺时针和逆时针均可),如果是契合的,小蓝还想让你告诉他,最少将第一个齿轮旋转多少位,两个齿轮可以完全契合在一起。
例如:AbbCd
与 BcDaB
,在将第一个齿轮逆时针旋转两位后,变成 bCdAb
,两个齿轮就完全契合在一起了。
输入格式
第一行输入一个正整数 n n n,代表两个齿轮的长度。
第二行输入一个长度为 n n n 的字符串 S S S,代表第一个齿轮。
第三行输入一个长度为 n n n 的字符串 T T T,代表第二个齿轮。
输出格式
第一行输出一个字符串:Yes
或者 No
。代表两个齿轮是否契合。
如果可以契合,第二行输出一个整数,代表需要旋转的位数。
如果不可以契合,不用多余输出。
样例输入
5
AbbCd
BcDaB
样例输出
Yes
2
评测数据范围
数据范围: 1 ≤ n ≤ 1 0 6 1 \le n \le 10^6 1≤n≤106
保证字符串只包含大小写字母。
2、基础框架
#include <iostream>
using namespace std;
int main()
{
// 请在此输入您的代码
return 0;
}
3、原题链接
契合匹配
二、解题报告
1、思路分析
首先可以做的事是将 S S S 串大小写转换,那么就变成了两个字符串的完全匹配。
首先,使用破环成链的思想,将 S S S 串扩大两倍成为 S ′ S' S′,将 T T T 串与 S ′ S' S′ 做匹配。如下代码:
// 将S串扩大两倍
for (int i = 1; i <= n; ++i) {
if (s[i] >= 'a' && s[i] <= 'z') {
s[i] += 'A' - 'a';
} else if (s[i] >= 'A' && s[i] <= 'Z') {
s[i] += 'a' - 'A';
}
s[i + n] = s[i];
}
然后使用 KMP 算法进行匹配,提供 KMP 算法代码:
void get_next(char *s, int *next, int n) {
next[1] = 0;
for (int i = 2, j = 0; i <= n; ++i) {
while (j > 0 && s[j + 1] != s[i]) j = next[j];
if (s[i] == s[j + 1]) ++j;
next[i] = j;
}
}
之后考虑匹配到的最前与最后的位置: P o s b e g i n Pos_{begin} Posbegin、 P o s e n d Pos_{end} Posend 。
然后对于两个位置,考虑顺时针与逆时针旋转需要的步数,取最优解即可。
算法本身并不复杂,主要考虑破环成链思想和 KMP 算法。
接下来简单介绍破环成链:
如果一个字符串
S
S
S (abcdef
)扩展成两倍
S
′
S'
S′ ( abcdefabcdef
),那么从
S
′
S'
S′ 的第二个字符开始
6
6
6 个字符组成的子串( bcdefa
),实际上就是
S
S
S 逆时针旋转
1
1
1 位的字符串,同时也是顺时针旋转
5
5
5 位的情况。
本题实质上就是 判断两个字符串是否互为旋转字符串。
对于KMP的讲解与实现见博客 KMP算法讲解与实现。
2、时间复杂度
O ( N ) O(N) O(N)
3、代码详解
/*************************************************************************
> File Name: my5_契合匹配.cpp
> Author: Maureen
> Mail: Maureen@qq.com
> Created Time: 日 10/15 18:48:14 2023
************************************************************************/
#include <iostream>
using namespace std;
//大写转换成小写,小写转换成大写
void strConvert(string &s) {
for (int i = 0; i < s.size(); i++) {
if (s[i] >= 'A' && s[i] <= 'Z') {
s[i] += 'a' - 'A';
} else if (s[i] >= 'a' && s[i] <= 'z') {
s[i] += 'A' - 'a';
}
}
}
int *getNextArray(string &t) {
int *next = new int[t.size()];
if (t.size() == 1) {
next[0] = -1;
return next;
}
next[0] = -1;
next[1] = 0;
int ind = 2; //当前待求信息的位置
int cn = 0; //前缀的下一个位置,也是要与ind-1位置进行匹配的位置
while (ind < t.size()) {
if (t[cn] == t[ind - 1]) {
next[ind++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[ind++] = 0;
}
}
return next;
}
int kmp(string &s, string &t) {
int si = 0;
int ti = 0;
int *next = getNextArray(t);
while (si < s.size() && ti < t.size()) {
if (s[si] == t[ti]) {
si++;
ti++;
} else if (next[ti] == -1) {
si++;
} else {
ti = next[ti];
}
}
return ti == t.size() ? si - ti : -1;
}
int main() {
int n;
cin >> n;
string s;
string t;
cin >> s >> t;
int oldSize = s.size();
//s字符串进行大小写转换
strConvert(s);
//扩充为两倍
s += s;
int posBegin = kmp(s, t);
int posEnd = oldSize + posBegin;
if (posBegin != -1) {
cout << "Yes" << endl;
cout << min(posBegin - 0, 2 * oldSize - posEnd);
} else {
cout << "No" << endl;
}
return 0;
}