例题:(1)AcWing 831. KMP字符串
。。其实写完也不太理解。。随便写点吧
KMP就是求next数组和运用next的数组的过程。相比传统匹配模式一次更新一单位距离的慢速方法,next数组可以让下表字符串一次更新n - next【n】个距离,加快了匹配速度。next【i】记录的是某字符串的前i个字符中前缀与后缀最长的匹配长度。
对于模版中的j,我的理解是已经匹配了j个字符,因此next【i】还可以理解为对于已经匹配了i个字符的字符串来说,假如后续匹配不成立,则该字符串至少还有next【i】个字符是匹配的。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10,M = 1e6+10;
int ne[N];
int n,m;
char p[N],s[M];
int main()
{
cin >> n >> p+1 >> m >> s+1;
ne[1] = 0;
for(int i = 2,j = 0;i<=n;i++){
while(j && p[i] != p[j+1]) j = ne[j];
if(p[i] == p[j+1]) j++;
ne[i] = j;
}
for(int i = 1,j = 0;i<=m;i++){
while(j && s[i] != p[j+1]) j = ne[j];
if(s[i] == p[j+1]) j++;
if(j == n){
printf("%d ",i - n);
j = ne[j];
}
}
return 0;
}
练习:(1) Leetcode 459 重复的子字符串
。。。好难啊!!!!这是easy???
我们可以证明 非空字符串s可由一个子串重复多次构成 当且仅当 把两个s连接起来且去掉前面后面的各一个字符,其中仍然存在s作为新字符串的子串。
从前往后推很显然,从后往前推需要一定证明。显然我不会,摘抄一下Leetcode的答案。
(2) P1470 [USACO2.3] 最长前缀 Longest Prefix
其实与KMP无关但不知道为什么加了个KMP的标签。。。没做出来。。
跟之前做过的一些题目有些类似。dp[i]的含义为[0,i-1]个字母可以分解,因此我们枚举每一个j,只要满足[0,j-1]时能分解且[j,i-1]也在P集合内即可。对于查询[j,i-1],由于题目说P集合内字符串长度<=10,因此我们对每一个i只需要枚举10个j即可,这样大大优化了时间。
以及不知为什么本地跑不了。。。(问了朋友可能是文件输入输出的关系)
#include <iostream>
#include <unordered_set>
#include <string>
using namespace std;
const int N = 2e5+10;
bool dp[N];
unordered_set<string> res;
int main(int argc, char** argv) {
string ss;
while(cin >> ss){
if(ss == ".") break;
res.insert(ss);
}
string ans = "";
while(cin >> ss){
ans += ss;
}
dp[0] = true;
int n = ans.length();
for(int i = 1;i<=n;i++){
for(int j = i;j>=max(i-10,0);j--){
if(dp[j] && (res.find(ans.substr(j,i-j))!=res.end())){
dp[i] = true;
continue;
}
}
}
for(int i = n;i>=0;i--){
if(dp[i]){
printf("%d",i);
break;
}
}
return 0;
}
(3) P3435 [POI2006] OKR-Periods of Words
太难了太难了。。。
一个前缀的最大周期长度就是其长度减去其最小匹配长度,也就是其最短的共同前后缀的长度。 next数组求的是最大匹配长度,而不断递归求next数组得到的正是不为0的最小匹配长度。递归处理时可以把next数组直接更新为求得的最短长度,这样求取速度会更快。
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1000010;
char str[N];
int ne[N];
int main(int argc, char** argv) {
int n;
scanf("%d%s",&n,str+1);
ne[1] = 0;
for(int i = 2,j = 0;i<=n;i++){
while(j && str[i] != str[j+1]) j = ne[j];
if(str[i] == str[j+1]) j++;
ne[i] = j;
}
ll ans = 0;
for(int i = 2,j = 2;i<=n;i++,j = i){
while(ne[j]) j = ne[j];
if(ne[i]) ne[i] = j;
ans += i-j;
}
printf("%lld",ans);
return 0;
}
(4) Leetcode 面试题17.17 多次搜索
对每一个smalls内的字符串求next数组,然后与big字符串进行kmp匹配。注意kmp的i返回的是字符串的最后一个字符下标。以及每一次匹配都要先清空next数组。
class Solution {
int ne[1010];
vector<vector<int>> res;
public:
void next(string str){
char x[1010];
int n = str.length();
for(int i = 1;i<=n;i++) x[i] = str[i-1];
for(int i = 2,j = 0;i<=n;i++){
while(j && x[i] != x[j+1]) j = ne[j];
if(x[i] == x[j+1]) j++;
ne[i] = j;
}
}
vector<int> get(string a,string b){
char x[1010],y[1010];
int n = a.length(),m = b.length();
for(int i = 1;i<=n;i++) x[i] = a[i-1];
for(int i = 1;i<=m;i++) y[i] = b[i-1];
vector<int> ans;
for(int i = 1,j = 0;i<=n;i++){
while(j && x[i] != y[j+1]) j = ne[j];
if(x[i] == y[j+1]) j++;
if(j == m){
ans.push_back(i-m);
j = ne[j];
}
}
return ans;
}
vector<vector<int>> multiSearch(string big, vector<string>& smalls) {
int n = smalls.size();
for(int i = 0;i<n;i++){
if(smalls[i] == ""){
res.push_back({});
continue;
}
memset(ne,0,sizeof(ne));
next(smalls[i]);
res.push_back(get(big,smalls[i]));
}
return res;
}
};
(5) Leetcode 3036 匹配模式数组的子数组数目
做出来的第一个Hard题!虽然完全不是自己想出来的!!。。。
把nums转化为跟pattern数组一样模式的长度为n-1的数组,此题便转化为求nums数组中有几个子数组为pattern数组,这样一来直接用kmp匹配即可。转化这一步还是挺难想到的。
class Solution {
int x[1000100],y[1000100],ne[1000100];// x,y 1-n
public:
int countMatchingSubarrays(vector<int>& nums, vector<int>& pattern) {
int n = nums.size()-1,m = pattern.size();
for(int i = 0;i<n;i++){
if(nums[i] < nums[i+1]) x[i+1] = 1;
else if(nums[i] == nums[i+1]) x[i+1] = 0;
else x[i+1] = -1;
}
for(int i = 1;i<=m;i++){
y[i] = pattern[i-1];
}
for(int i = 2,j = 0;i<=m;i++){
while(j && y[i] != y[j+1]) j = ne[j];
if(y[i] == y[j+1]) j++;
ne[i] = j;
}
int ans = 0;
for(int i = 1,j = 0;i<=n;i++){
while(j && x[i] != y[j+1]) j = ne[j];
if(x[i] == y[j+1]) j++;
if(j == m){
ans++;
j = ne[j];
}
}
return ans;
}
};
(6) Leetcode 3037.在无限流中寻找模式
做出的第二道Hard题,想出了匹配方式但是在优化上栽了!不过还算可以!
由于是无限流,所以每次更新就匹配一次肯定会超时,因此用vector处理,由于next数组是确定的,因此每次不用从头匹配,只需要一个一个进行匹配即可。
/**
* Definition for an infinite stream.
* class InfiniteStream {
* public:
* InfiniteStream(vector<int> bits);
* int next();
* };
*/
class Solution {
int ne[110000];
vector<int> p,q;
public:
int findPattern(InfiniteStream* stream, vector<int>& pattern) {
p.push_back(0);
q.push_back(0);
int r = 0,m = pattern.size();
for(int i = 1;i<=m;i++){
q.push_back(pattern[i-1]);
}
for(int i = 2,j = 0;i<=m;i++){
while(j && q[i] != q[j+1]) j = ne[j];
if(q[i] == q[j+1]) j++;
ne[i] = j;
}
int i = 1,j = 0;
while(1){
p.push_back(stream->next());
while(j && p[i] != q[j+1]) j = ne[j];
if(p[i] == q[j+1]) j++;
if(j == m) return i-m;
i++;
}
}
};
(7) Leetcode 3008.找出数组中的美丽下标
Hard被卡了。。
匹配用kmp,后续查找时的优化思路为对a中每一个下标数组,在b的下标数组二分查找第一个大于等于它的数,如果这个数存在则返回其与a中对应下标的差,和其前面一个与a中对应下标的差,如果满足则返回。
class Solution {
char p[500100],q[500100],r[500010];
int ne1[500100],ne2[500100];
public:
vector<int> beautifulIndices(string s, string a, string b, int k) {
vector<int> res;
vector<int> res1,res2;
int n = a.length(),m = b.length(),o = s.length();
for(int i = 1;i<=n;i++) p[i] = a[i-1];
for(int i = 1;i<=m;i++) q[i] = b[i-1];
for(int i = 1;i<=o;i++) r[i] = s[i-1];
for(int i = 2,j = 0;i<=n;i++){
while(j && p[i] != p[j+1]) j = ne1[j];
if(p[i] == p[j+1]) j++;
ne1[i] = j;
}
for(int i = 2,j = 0;i<=m;i++){
while(j && q[i] != q[j+1]) j = ne2[j];
if(q[i] == q[j+1]) j++;
ne2[i] = j;
}
for(int i = 1,j = 0;i<=o;i++){
while(j && r[i] != p[j+1]) j = ne1[j];
if(r[i] == p[j+1]) j++;
if(j == n){
res1.push_back(i-n);
j = ne1[j];
}
}
for(int i = 1,j = 0;i<=o;i++){
while(j && r[i] != q[j+1]) j = ne2[j];
if(r[i] == q[j+1]) j++;
if(j == m){
res2.push_back(i-m);
j = ne2[j];
}
}
int c1 = res1.size(),c2 = res2.size();
for (int i: res1) {
auto it = lower_bound(res2.begin(), res2.end(), i);
if (it != res2.end() && *it - i <= k ||
it != res2.begin() && i - *--it <= k) {
res.push_back(i);
}
}
return res;
}
};
(8) Leetcode 758.字符串中的加粗单词
没啥难的。。挑错时间太长所以贴上来羞辱一下自己。。
kmp加合并区间。首先先对words每一个字符串求next然后与s进行匹配,求出每一个需要加粗的区间。由于这些区间存在重叠而导致标签数过多,因此要求最小的标签数就需要合并区间。
class Solution {
int ne[15];
typedef pair<int,int> pii;
public:
vector<pii> segs;
vector<char> init(string x){
vector<char> p;
p.push_back(0);
int n = x.length();
for(int i = 1;i<=n;i++) p.push_back(x[i-1]);
return p;
}
void next(vector<char> x){
int n = x.size()-1;
for(int i = 2,j = 0;i<=n;i++){
while(j && x[i] != x[j+1]) j = ne[j];
if(x[i] == x[j+1]) j++;
ne[i] = j;
}
}
void kmp(vector<char> x,vector<char> y){
int n = x.size() - 1,m = y.size() - 1;
for(int i = 1,j = 0;i<=n;i++){
while(j && x[i] != y[j+1]) j = ne[j];
if(x[i] == y[j+1]) j++;
if(j == m){
segs.push_back({i-m,i-1});
j = ne[j];
}
}
}
string boldWords(vector<string>& words, string s) {
int n = words.size();
auto p = init(s);
for(int i = 0;i<n;i++){
memset(ne,0,sizeof(ne));
auto q = init(words[i]);
next(q);
kmp(p,q);
}
vector<pii> res;
sort(segs.begin(),segs.end());
int fs = -2e9,ed = -2e9;
for(auto seg:segs){
int l = seg.first,r = seg.second;
if(ed+1<l){
if(fs!=-2e9) res.push_back({fs,ed});
fs = l;
ed = r;
}
else{
ed = max(ed,r);
}
}
if(fs!=-2e9) res.push_back({fs,ed});
int flag = 0;
string ans = "";
for(auto seg:res){
int l = seg.first,r = seg.second;
ans += s.substr(flag,l - flag);
ans += "<b>";
ans += s.substr(l,r-l+1);
ans += "</b>";
flag = r+1;
}
n = s.length();
cout <<flag;
if(flag < n ) ans += s.substr(flag,n-flag);
return ans;
}
};
(9) P2375 [NOI2014] 动物园 很难,虽然写出来了但并不完全理解。。
在求取next数组的过程中,我们可以求取每一个i所对应的有相同的前后缀的所有子串的个数,然后再进行一次匹配,当我们递归到子串长度不重叠时,就可以得到该长度下所有的不重叠的子串有相同前后缀的个数。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e6+10,mod = 1e9+7;
typedef long long ll;
char p[N];
int ne[N],nums[N];
int main(int argc, char** argv) {
int t;
scanf("%d",&t);
while(t--){
scanf("%s",p+1);
int n = strlen(p+1);
nums[1] = 1;
for(int i = 2,j = 0;i<=n;i++){
while(j && p[i] != p[j+1]) j = ne[j];
if(p[i] == p[j+1]) j++;
ne[i] = j;
nums[i] = nums[ne[i]] + 1;
}
ll res = 1;
for(int i = 1,j = 0;i<=n;i++){
while(j && p[i]!=p[j+1]) j = ne[j];
if(p[i] == p[j+1]) j++;
while(j * 2 > i) j = ne[j];
res = res * (nums[j] + 1) % mod;
}
printf("%lld\n",res);
for(int i = 1;i<=n;i++){
ne[i] = 0;
nums[i] = 0;
}
}
return 0;
}