两个示例弄懂KMP(Java)

两个示例弄懂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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值