字符串
- 一、KMP
- 1、模板题 HDU1711 Number Sequence
- 2、求最大匹配数 Ⅰ: HDU 2087 剪花布条(子串不重叠)
- 3、求最大匹配数 Ⅱ:AcWing 831. KMP字符串(子串可重叠)
- 4、s2 是不是 s1 的翻转:Leetcode 面试题 01.09. 字符串轮转
一、KMP
1、模板题 HDU1711 Number Sequence
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1711
题目大意
找出子串第一次出现的位置,找不到输出 - 1.
next 数组的含义:
next [ i ] 表示:以 i 为终点,以 1 为起点,前后缀能一致的最长字串。
(在某些头文件有命名过next,所以代码里面以 ne 代表next)
next [ i ] = x 表示:如果匹配到 idx = i 的时候 str [ i ] != p [ i ],那么在 str
第 i 个字符的前面有 x 个字符不用再重新匹配,可以直接拿 str [ i ] 和 p [ x + 1 ] 开始比较,循环往复。
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <climits>
using namespace std;
#define getlen(array) {return (sizeof(array) / sizeof(array[0]));}
#define ll long long
#define MEM(x, y) memset(x, y, sizeof x)
#define rin int n; scanf("%d", &n)
#define rln ll n; scanf("%lld", &n)
#define rit int t; scanf("%d", &t)
#define sc scanf
#define pr printf
const int INF = 0x3f3f3f3f;
const int N = 1000010, M = 10010;
//(val & 1) == 0偶, == 1奇。
int str[N]; //文本串
int p[M]; //匹配串
int ne[M]; //next数组
//获得匹配串的next数组
void make_nextArr(int m) {
MEM(ne, 0);
for (int i = 2, j = 0; i <= m; ++ i) {
//获得当前的 i 能和 p 数组里面最右的哪个数匹配
while (j && p[i] != p[j + 1]) j = ne[j];
//能匹配则 if 为真
if (p[i] == p[j + 1]) ++ j;
//记录到ne数组
ne[i] = j;
}
}
//kmp的过程
void KMP (int n, int m) {
bool flag = true;
for (int i = 1, j = 0; i <= n; ++ i) {
while (j && str[i] != p[j + 1]) j = ne[j];
if (str[i] == p[j + 1]) ++ j;
if (j == m) { //j == m 说明匹配到公共子串了
pr("%d\n", i - m + 1);
flag = false;
break;
}
}
if (flag) pr("-1\n");
}
int main() {
//freopen("D:\\in.txt", "r", stdin);
//freopen("D:\\out.txt", "w", stdout);
rit;
while (t --) {
int n, m;
sc("%d %d", &n, &m);
for (int i = 1; i <= n; ++ i) sc("%d", &str[i]);
for (int i = 1; i <= m; ++ i) sc("%d", &p[i]);
make_nextArr(m);
KMP(n, m);
}
return 0;
}
2、求最大匹配数 Ⅰ: HDU 2087 剪花布条(子串不重叠)
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=2087
题目大意
找到子串最大数目,子串间不可重叠。
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <climits>
using namespace std;
#define getlen(array) {return (sizeof(array) / sizeof(array[0]));}
#define ll long long
#define MEM(x, y) memset(x, y, sizeof x)
#define rin int n; scanf("%d", &n)
#define rln ll n; scanf("%lld", &n)
#define rit int t; scanf("%d", &t)
#define sc scanf
#define pr printf
const int INF = 0x3f3f3f3f;
const int N = 1010;
//(val & 1) == 0偶, == 1奇。
char str[N], p[N];
int ne[N];
int cnt;
void make_next(int m) {
for (int i = 2, j = 0; i <= m; ++ i) {
while (j && p[i] != p[j + 1]) j = ne[j];
if(p[i] == p[j + 1]) ++ j;
ne[i] = j;
}
}
//在下标为 start 的地方开始kmp
void KMP (int n, int start, int m) {
if (start > n) return; // 开始的下标不合法即结束
int i = start;
for (int j = 0; i <= n; ++ i) {
while (j && str[i] != p[j + 1]) j = ne[j];
if (str[i] == p[j + 1]) ++ j;
if (j == m) {
++ cnt;
break;
}
}
KMP(n, i + 1, m); //递归获得最大匹配次数
}
int main() {
//freopen("D:\\in.txt", "r", stdin);
//freopen("D:\\out.txt", "w", stdout);
str[0] = 6, p[0] = 6; //防止 strlen 为 0
while (true) {
sc("%s", str + 1);
getchar();
int n = strlen(str) - 1;
if (n == 1 && str[1] == '#') break;
sc("%s", p + 1);
int m = strlen(p) - 1;
make_next(m);
cnt = 0; //记得置零
KMP(n, 0, m);
pr("%d\n", cnt);
}
return 0;
}
3、求最大匹配数 Ⅱ:AcWing 831. KMP字符串(子串可重叠)
原题链接:https://www.acwing.com/problem/content/description/833/
思路
主要是在kmp的时候,当找到和模板串一模一样的子串时,直接让 j = ne [ j ] ,否则对于一些苛刻的数据会超时,例如字符串全是同一个字符的时候。
代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <unordered_set>
#include <unordered_map>
using namespace std;
#define getlen(array) {return (sizeof(array) / sizeof(array[0]));}
#define ll long long
#define MEM(x, y) memset(x, y, sizeof x)
#define rin int n; scanf("%d", &n)
#define rln ll n; scanf("%lld", &n)
#define rit int t; scanf("%d", &t)
#define ria int a; scanf("%d", &a)
#define sc scanf
#define pr printf
const int INF = 0x3f3f3f3f;
const int N = 100010, M = 1000010;
//(val & 1) == 0偶, == 1奇。
char p[N], s[M];
int ne[N];
void get_next(int length) {
for (int i = 2, j = 0; i <= length; ++ i) {
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) ++ j;
ne[i] = j;
}
}
void kmp(int n, int a) {
for (int i = 1, j = 0; i <= a; ++ i) {
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) ++ j;
if (j == n) {
pr("%d ", i - n);
j = ne[j]; // 最关键步骤
}
}
}
int main() {
//freopen("D:\\in.txt", "r", stdin);
//freopen("D:\\out.txt", "w", stdout);
rin;
sc("%s", p + 1);
ria;
sc("%s", s + 1);
get_next(n);
kmp(n, a);
return 0;
}
4、s2 是不是 s1 的翻转:Leetcode 面试题 01.09. 字符串轮转
原题链接:https://leetcode-cn.com/problems/string-rotation-lcci/
思路
这道题目最大的精髓在于将 s2 和 s2相连后,如果 s2 是 s1 翻转后的字符串,那么新拼接而成的字符串一定存在某个字串是 s1,后面的事情就直接 kmp。
(当然也可以直接调用字符串本身的 find 函数……)
代码
kmp 版:
class Solution {
public:
int ne[100010];
bool isFlipedString(string s1, string s2) {
if (s1.size() != s2.size()) return false;
if (s1 == "" && s2 == "") return true;
s1 = ' ' + s1;
s2 = s2 + s2;
s2 = ' ' + s2;
get_next(s1);
return kmp(s1, s2);
}
void get_next(string s) {
for (int i = 2, j = 0; s[i]; ++ i) {
while (j != 0 && s[i] != s[j + 1]) j = ne[j];
if (s[i] == s[j + 1]) ++ j;
ne[i] = j;
}
}
bool kmp(string p, string s) {
int length = p.size() - 1;
for (int i = 1, j = 0; s[i]; ++ i) {
while (j != 0 && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) ++ j;
if (j == length) return true;
}
return false;
}
};
调用函数版:
class Solution {
public:
bool isFlipedString(string s1, string s2) {
return s1.size() == s2.size() && (s2 + s2).find(s1) != -1;
}
};
————————————————————
2021.02.26 学习KMP,匹配数Ⅰ
2021.03.25 KMP匹配数Ⅱ
2021.03.29 KMP - 翻转