ACM - 字符串 - 基础(KMP)

一、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 - 翻转

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值