KMP总结归纳 题型汇总(kuangbin带你飞专题16)

前序:花了好几天才把带你飞系列的KMP专题(地址:https://vjudge.net/contest/246969#overview搞定,刷了很多题,发现KMP可以用来解决这几类问题:

①单个字符串匹配问题(s1在s2中匹配):这个用strstr(const char* big, const char* small)函数也可以,这个函数返回small在big函数中首次出现的地址,没有出现就返回NULL。区别在于:

1️⃣如果只是求s1是否在s2中出现过的话,建议可以用strstr函数,这个函数的复杂度还是可以接受的(如果数据量达到1e6就还是老老实实用kmp,用这个函数会TLE,例如A题),如果实在不行,再换用kmp模板吧。当然strstr只能适用字符数组,若是数字就直接上kmp吧,当然,强行把数字用字符存也不是不可以,QAQ。

2️⃣如果求s1在s2中出现了几次(分为可重叠与不可重叠),就只能用kmp了。

3️⃣如果求s1在s2首次出现的下标,用kmp可以直接返回下标,而strstr函数貌似也可以,不过由于返回的是地址,需要转化一下。

附上kmp求以上问题的kmp_pre的各种变形

int KMP_Count(char s[], char p[]){
	kmp_pre();
	cnt = 0;
	int i,j;
	i = j = 0;
	int  n = strlen(s);
	int m = strlen(p);
	while(i < n){
		while(j != -1 && s[i] != p[j])	j = nextt[j];		
		++i;  ++j;
		if(j >= m){		
			cnt++;              //记录p在s中出现的个数             
			j = nextt[j];		//p在s中可以覆盖、重叠,例如s=abababa p=aba,最后cnt=3
		    //j = 0;			//p在s中不可以覆盖、重叠,例如s=abababa p=aba,最后cnt=2
               //return 1;         //返回存在
           //return i-m+1;      //返回p首次在s中出现的下标
		}
	}
    return cnt;                 //返回出现次数
    //return 0;                 //返回不存在
   // return -1;                   //p首次在s中出现的下标为-1,表示s中没有p
}

②字符串循环节问题(nextt数组的运用一):感觉kmp最牛的地方就是nextt数组了,nextt数组又称前缀数组(或许叫做最长前后缀数组更加合适),先看张图哈:

可以看出nextt[i]其实就是字符串s[0~(i-1)]的前后缀的最大相等长度。需要注意的是nextt[0]=-1,nextt[0]=0是不变的,至于原因,结合nextt的定义不难理解。还有就是上图中,其实nextt[6]=4,可以发现4就是字符串ababab的前后缀的最大相等长度,而6(字符串长度)-4=2即为循环节的长度。所以循环节的长度公式为:l = len-nextt[len],其中len为字符串的长度。这里也要分两种情况

1️⃣len%l == 0:就是字符串的长度是循环节的整数倍,有一种比较特殊的情况就是l == len这种类型的伪循环,例如abcde,它的循环节就是它本身,等于说并没有循环。而常见的是类似于abcbac这种(len = 6, l = 3)。有时候还会求周期个数(循环节个数):num = len/i;

2️⃣len%l != 0,就是字符串不是循环节的整数倍,没有循环到底,例如abcabcab(注意这里的nextt[8]=5(abcab),所以l = 8-5=3),

8%3==2 != 0,就是说最后一个循环节没有循环到底,只有2个(ab),要补上l-len%l=1个

故涉及到循环节的问题,只要考虑三组类型:abcabc,abcabcab,abcdef就可以避免考虑不全的情况了。

③前后缀问题(nextt数组的应用二):

1️⃣nextt数组的定义见②,就是前后缀的最大相等长度,通过一个while循环,不断迭代,直到nextt[i]为0,可以求出字符串所有的前后缀相等的长度。例如,ababcababababcabab,最长的相等长度为nextt[18]=9(ababcabab),然后nextt[9]=4(abab),nextt[4]=2(ab), nextt[2]=0。注意,括号的字符串既可以理解为前缀,也可以理解为原字符串的前后缀的组合(由于之前前后缀的匹配带来的关系)。

2️⃣扩展kmp:扩展kmp中用到了两个数组:nextt和ext,其中nextt[i]数组的定义是:p[i.....(m-1)]与p[0....(m-1)]的最长公共前缀,也就是p串从i开始后面的字符串p1与整个p串的最长公共前缀。注意与kmp算法中的nextt数组的区别:kmp算法中的nextt[i]表示的是字符串p[0...(i-1)]这个字符串的最大相等前后缀的长度。举个栗子:abaabb。在kmp算法中,nextt的值除了nextt[0]=-1外,其余都为0。而在扩展kmp中,nextt[3]=2,因为nextt[3]表示的是字符串p1[3...5](即abb)与p(即abaabb)的最大公共前缀的长度,也就是2。

它的nextt[i]=m可以理解为p字符串前面m个字符在后面的第i个位置又出现了。 不难理解:nextt[0]总是等于m。

ext[i]数组:定义是s[i....(n-1)]与p[0....(m-1)]的最大公共前缀。也就是主串s的从i开始的后面的字符串s1与模式串p的最长公共前缀。当ext[i]=m时,也就是s1==p,说明p串在s串的第i个位置出现了,也就是kmp中的匹配成功,所以说扩展kmp是kmp的一种扩展

ps:扩展kmp其实求得的是两个字符串的最大前缀,不过可以转化为两个字符串的最大相等前后缀长度(没错,就是上面kmp算法的nextt的应用)。具体就是加一个判断当if(ext[i]==n-i) 此时s[i....(n-1)]字符串就是s串的后缀,并且和p串的长度为ext[i]的前缀相等。

3️⃣环形串(可以从不同的下标开始形成n个串)性质:

①就是环形串的n个串的个数都一样,特别的,当原串是类似于abcabc这种“满循环串”时,每个串的个数就等于(len/循环节),也就是周期个数,而串的种类就等于一个循环节的长度。

②涉及到环串,还有一个经常用到的处理方式,就是倍增一次,将s串变成s+s,这样就不用取余什么的操作了。(这题没用到)

 

A - Number Sequence

题意:

给你字符串s和p,问你p在s串中首次出现的下标,若没有出现,则返回-1。

思路:裸单个字符串匹配问题(s1在s2中匹配,①1️⃣类型),一开始直接用kmp写得,刚刚试了一下,TLE了,这题数据量1e6,T也是意料之中,所以数据量很大时,还是老老实实敲kmp板子吧。

AC代码(kmp板子):

#include <map>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#define Fin             freopen("in.txt","r",stdin)
#define Fout            freopen("out.txt","w",stdout)
#define Case(T)         int T;for(scanf("%d",&T);T--;)
#define fo(i,a,b)              for(int i = a; i < b; ++i)
#define fd(i,a,b)              for(int i = a; i >= b; --i)
#define me(a,b) memset(a,b,sizeof(a))
#define fi(a,n,val)    fill(a,a+n,val);
#define Scand(n)       scanf("%d",&n);
#define Scans(s)       scanf("%s",s);
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b ? gcd(b,a%b): a; }
const int maxn = 1e6 + 500;
const int INF = 0xffffff;

#ifndef ONLINE_JUDGE

#endif // ONLINE_JUDGE

int n,m;
int s[maxn], p[maxn];
int nextt[maxn];

void  kmp_pre(){
    int i,j;
    j = nextt[0] = -1;
    i = 0;
    while(i < m){
        while (j != -1 && p[i] != p[j])  j = nextt[j];
        nextt[++i] = ++j;
    }
}

int kmp(){
    kmp_pre();
    int i,j;
    i = j = 0;
    while (i < n) {
        while (j != -1 && s[i] != p[j]) j = nextt[j];
        ++i; ++j;
        if(j >= m){
            return  i;
        }
    }
    return  -1;
}

int main()
{
    //ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
   // Fin;
#endif // ONLINE_JUDGE
    Case(T){
        scanf("%d%d",&n,&m);
        fo(i,0,n)
            Scand(s[i]);
        fo(i,0,m)
            Scand(p[i]);
        me(nextt,0);
        int ans = kmp();
        if(ans != -1)
            printf("%d\n",ans-m+1);
        else
            printf("%d\n",ans);
    }
    return 0;
}

TLE代码(用的strstr函数):

#include <map>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#define Fin             freopen("in.txt","r",stdin)
#define Fout            freopen("out.txt","w",stdout)
#define Case(T)         int T;for(scanf("%d",&T);T--;)
#define fo(i,a,b)              for(int i = a; i < b; ++i)
#define fd(i,a,b)              for(int i = a; i >= b; --i)
#define me(a,b) memset(a,b,sizeof(a))
#define fi(a,n,val)    fill(a,a+n,val);
#define Scand(n)       scanf("%d",&n);
#define Scans(s)       scanf("%s",s);
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b ? gcd(b,a%b): a; }
const int maxn = 1e6 + 500;
const int INF = 0xffffff;

#ifndef ONLINE_JUDGE

#endif // ONLINE_JUDGE

int n,m;
int s[maxn], p[maxn];
int nextt[maxn];

void  kmp_pre(){
    int i,j;
    j = nextt[0] = -1;
    i = 0;
    while(i < m){
        while (j != -1 && p[i] != p[j])  j = nextt[j];
        nextt[++i] = ++j;
    }
}

int kmp(){
    kmp_pre();
    int i,j;
    i = j = 0;
    while (i < n) {
        while (j != -1 && s[i] != p[j]) j = nextt[j];
        ++i; ++j;
        if(j >= m){
            return  i;
        }
    }
    return  -1;
}

int main()
{
    //ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
    //Fin;
#endif // ONLINE_JUDGE
    Case(T){
        scanf("%d%d",&n,&m);
        fo(i,0,n)
            Scand(s[i]);
        fo(i,0,m)
            Scand(p[i]);

        char ss[maxn];
        char pp[maxn];
        fo(i, 0, n){
            ss[i] = s[i]+'1'-1;
        }
        fo(i, 0, m){
            pp[i] = p[i]+'1'-1;
        }
        char* ans = strstr(ss, pp);
        if(ans == NULL)
            cout << -1 << endl;
        else
            cout << distance(ss, ans)+1 << endl;     //利用distance函数根据地址求距离
    }
    return 0;
}

B - Oulipo

题意:

给你两个字符串s和p,问你s中有多少个p(可重叠)?

思路:裸的kmp板子(①2️⃣类型)。

代码:

#include <map>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#define Fin             freopen("in.txt","r",stdin)
#define Fout            freopen("out.txt","w",stdout)
#define Case(T)         int T;for(scanf("%d",&T);T--;)
#define fo(i,a,b)              for(int i = a; i < b; ++i)
#define fd(i,a,b)              for(int i = a; i >= b; --i)
#define me(a,b) memset(a,b,sizeof(a))
#define fi(a,n,val)    fill(a,a+n,val);
#define Scand(n)       scanf("%d",&n);
#define Scans(s)       scanf("%s",s);
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b ? gcd(b,a%b): a; }
const int maxn = 1e6 + 5;
const int INF = 0xffffff;

#ifndef ONLINE_JUDGE

#endif // ONLINE_JUDGE

char s[maxn],p[maxn];
int n,m;
int nextt[maxn];


void kmp_pre(){
    int i,j;
    j = nextt[0] = -1;
    i = 0;
    while (i < m) {
        while (j != -1 && p[i] != p[j]) {
            j = nextt[j];
        }
        nextt[++i] = ++j;
    }
}

int kmp(){
    kmp_pre();
    int i,j;
    i = j = 0;
    int cnt = 0;
    while (i < n) {
        while (j != -1 && s[i] != p[j]) {
            j = nextt[j];
        }
        ++i; ++j;
        if(j >= m){
            cnt++;
            j = nextt[j];
        }
    }
    return  cnt;
}

int main()
{
#ifndef ONLINE_JUDGE
    //Fin;
#endif // ONLINE_JUDGE
    Case(T){
        scanf("%s",p);  scanf("%s",s);
        n = strlen(s); m = strlen(p);
        int ans = kmp();
        printf("%d\n",ans);
    }
    return 0;
}

C - 剪花布条

 

题意:

给你两个字符串s和p,问你s中有多少个p(不可重叠)?

思路:裸的kmp板子(①2️⃣类型)。

代码:

#include <map>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#define Fin             freopen("in.txt","r",stdin)
#define Fout            freopen("out.txt","w",stdout)
#define Case(T)         int T;for(scanf("%d",&T);T--;)
#define fo(i,a,b)              for(int i = a; i < b; ++i)
#define fd(i,a,b)              for(int i = a; i >= b; --i)
#define me(a,b) memset(a,b,sizeof(a))
#define fi(a,n,val)    fill(a,a+n,val);
#define Scand(n)       scanf("%d",&n);
#define Scans(s)       scanf("%s",s);
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b ? gcd(b,a%b): a; }
const int maxn = 10000 + 5;
const int INF = 0xffffff;

#ifndef ONLINE_JUDGE

#endif // ONLINE_JUDGE

char s[maxn],p[maxn];
int n,
<think>好的,用户想了解蓝桥杯Java B组的常见题型。首先,我需要回忆之前提供的引用内容,看看有没有相关的信息。引用1提到蓝桥杯Java选手常用集合API,引用2是参赛者的备赛经历,引用3是关于星期计算的真题解析。这些内容虽然涉及备赛策略和具体题目,但没有直接列出所有常见题型。因此,我需要基于一般蓝桥杯Java B组的考试结构来总结常见题型。 蓝桥杯Java B组的题目通常分为填空题和编程题。填空题可能涉及数学计算、日期处理、排列组合等,需要精确的结果。编程题则覆盖算法和数据结构,比如动态规划、搜索、贪心、图论等。此外,大数处理和字符串操作也是常见的考点。 用户可能希望了解每个题型的具体例子和解题技巧,以及如何高效备考。例如,引用2提到刷题和真题练习的重要性,引用3展示了如何处理大数运算和模运算。需要结合这些信息,给出结构化的题型总结,并附上相关问题帮助用户深入理解。 要注意使用正确的中文表达,确保LaTeX格式无误,比如数学公式用$...$,独立公式用$$...$$。同时,根据用户提供的引用,在适当的地方添加引用标识,例如提到备赛策略时引用[^2],大数处理引用[^3]。 最后,生成的相关问题要针对用户可能的后续疑问,比如如何高效刷题、动态规划的解题步骤等,帮助用户全面准备比赛。</think>### 蓝桥杯Java B组常见题型总结 蓝桥杯Java B组的题目通常分为**填空题**和**编程题**两大类,内容覆盖基础算法、数据结构、数学思维及实际应用。以下是对常见题型的总结: --- #### 一、填空题 1. **数学计算与模运算** 需要快速推导公式并编写代码验证结果,例如: - 计算大数取模(如$20^{22} \mod 7$[^3]) - 日期问题(星期计算、闰年判断等) ```java // 示例:快速幂取模(计算 a^b mod p) int mod = 1; for (int i = 0; i < b; i++) { mod = (mod * a) % p; } ``` 2. **排列组合与数论** - 组合数计算(动态规划或公式法) - 质数筛法(埃氏筛、欧拉筛) - 最大公约数(GCD)、最小公倍数(LCM) --- #### 二、编程题 1. **动态规划(DP)** - 背包问题(01背包、完全背包) - 最长公共子序列(LCS) - 矩阵路径问题(如最小路径和) 2. **搜索算法** - DFS/BFS(迷宫问题、岛屿数量) - 回溯法(全排列、N皇后) 3. **贪心算法** - 区间调度(选择不相交区间) - 分配问题(如分发饼干) 4. **图论** - 最短路径(Dijkstra、Floyd) - 最小生成树(Prim、Kruskal) 5. **字符串处理** - 正则表达式匹配 - KMP算法(子串搜索) 6. **大数运算** Java中常用`BigInteger`和`BigDecimal`处理超出`long`范围的计算。 --- #### 三、高频考点 1. **集合API的应用** 如`List`、`Map`、`Set`的灵活使用(快速查询、去重)[^1]。 ```java // 示例:使用Map统计频率 Map<Integer, Integer> freq = new HashMap<>(); for (int num : nums) { freq.put(num, freq.getOrDefault(num, 0) + 1); } ``` 2. **递归与分治** 如快速排序、归并排序的实现[^1]。 3. **位运算优化** 如利用位掩码表示状态。 --- #### 四、备赛建议 1. **刷题策略** - 前期以力扣为主,后期转向蓝桥杯真题。 - 注重时间复杂度和空间复杂度的优化。 2. **代码规范** - 熟悉Java输入输出模板(如`Scanner`与`BufferedReader`的差异)。 - 注意边界条件(如数组越界、空值处理)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值