两个示例弄懂KMP(Java)
KMP简介
KMP适用的问题:字符串匹配问题
KMP核心操作:
为模式串构建next数组,简化比较步骤,跳过绝不可能成功的字符串比较。
构建next数组的核心思想:对模式串中的第i个字符,其前缀j个字符组成的子串与后缀j个字符组成的子串相等。
也即对模式串p,有:p[1~j] = p[i-j+1~i](索引从1开始)
详细的KMP解析和思考见:https://www.zhihu.com/question/21923021/answer/1032665486
示例一:实现strStr()(Leetcode 28)
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
示例 1:
输入:haystack = “hello”, needle = “ll”
输出:2
示例 2:
输入:haystack = “aaaaa”, needle = “bba”
输出:-1
示例 3:
输入:haystack = “”, needle = “”
输出:0
提示:
0 <= haystack.length, needle.length <= 5 * 104
haystack 和 needle 仅由小写英文字符组成
思路:常规的KMP字符匹配的思路,返回第一个匹配成功的索引。
解释下next数组的构建过程,模式串为needle,其长度设为m,则初始化next数组大小为m+1(设索引从1开始)。
next[1] = 0 :第一位匹配不成功,直接从头匹配。
注意这里我们进行比较用的是主串的第i位和模式串的第j+1位进行比较(用索引时分别减一)。
所以在计算next数组时用同样的逻辑,由于j = next[j]每次都会让j变小,递归可求出满足条件的next[i]最大值。
int[] next = new int[m+1];
for(int i = 2, j = 0; i <= m; i++){
while(j!=0&&needle.charAt(i-1)!=needle.charAt(j)){
j = next[j];
}
if(needle.charAt(i-1)==needle.charAt(j)){
j++;
}
next[i] = j;
}
匹配的部分一致,遇到不能匹配的位置回退到next[j]继续匹配,当匹配到最后一位时返回索引。
class Solution {
public int strStr(String haystack, String needle) {
if(needle.length()==0){
return 0;
}
int n = haystack.length();
int m = needle.length();
int[] next = new int[m+1];
for(int i = 2, j = 0; i <= m; i++){
while(j!=0&&needle.charAt(i-1)!=needle.charAt(j)){
j = next[j];
}
if(needle.charAt(i-1)==needle.charAt(j)){
j++;
}
next[i] = j;
}
for(int i = 1, j = 0; i <= n; i++){
while(j!=0&&haystack.charAt(i-1)!=needle.charAt(j)){
j = next[j];
}
if(haystack.charAt(i-1)==needle.charAt(j)){
j++;
}
if(j==m){
return i-m;
}
}
return -1;
}
}
示例二: KMP字符串(acwing 831)
给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1≤N≤10^5
1≤M≤10^6
输入样例:
3
aba
5
ababa
输出样例:
0 2
思路:整体的思路和上一题一致,不同之处在于此题要求输出所有可能的匹配索引,每次匹配到了用j=next[j]继续匹配即可。
import java.util.*;
class Main{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
String p = scan.next();
int m = scan.nextInt();
String s = scan.next();
int[] next = new int[n+1];
for(int i = 2, j= 0; i <= n; i++){
while(j!=0&&p.charAt(i-1)!=p.charAt(j)){
j = next[j];
}
if(p.charAt(i-1)==p.charAt(j)){
j++;
}
next[i] = j;
}
List<Integer> res = new ArrayList<>();
for(int i = 1, j= 0; i <= m; i++){
while(j!=0&&s.charAt(i-1)!=p.charAt(j)){
j = next[j];
}
if(s.charAt(i-1)==p.charAt(j)){
j++;
}
if(j==n){
res.add(i-n);
j = next[j];
}
}
for(int i : res){
System.out.print(i+" ");
}
}
}
使用Scanner不能过所有用例,需要用buffer:
import java.util.*;
import java.io.*;
class Main {
static int N = 100010;
static int M = 1000010;
static char[] ps = new char[N];
static int[] ne = new int[N];
static char[] ss = new char[M];
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
int n = Integer.parseInt(br.readLine());
char[] cs = br.readLine().toCharArray();
System.arraycopy(cs, 0, ps, 1, n);
int m = Integer.parseInt(br.readLine());
cs = br.readLine().toCharArray();
System.arraycopy(cs, 0, ss, 1, m);
for (int i = 2, j = 0; i <= n; i++) {
while (j != 0 && ps[i] != ps[j + 1]) {
j = ne[j];
}
if (ps[i] == ps[j + 1]) {
j++;
}
ne[i] = j;
}
// System.out.println(Arrays.toString(ne));
for (int i = 1, j = 0; i <= m; i++) {
while (j != 0 && ss[i] != ps[j + 1]) {
j = ne[j];
}
if (ss[i] == ps[j + 1]) {
j++;
}
if (j == n) {
bw.write(i - n + " ");
j = ne[j];
}
}
bw.flush();
}
}
作者:巨鹿
链接:https://www.acwing.com/activity/content/code/content/230785/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。