KMP算法——子串匹配

目录

1、什么是前缀函数

2、如何求前缀函数

3、解题思路

4、代码


KMP算法是一种字符串匹配算法,大大提高了字符串匹配的效率

例题:力扣28. 实现 strStr()以下是根据该题的题解而写

1、什么是前缀函数

前缀函数是KMP算法的核心,记作 π(i),其定义如下:对于长度为m的字符串,其前缀函数 π(i)(0≤i<m) 表示 ss 的子串 s[0:i] 的最长的相等的真前缀与真后缀的长度(真前缀、真后缀就是不等于自身的的前缀与后缀)。特别地,如果不存在符合条件的前后缀,那么 π(i)=0。

我们举个例子说明:字符串 aabaaab 的前缀函数值依次为 0,1,0,1,2,2,3。

π(0)=0,因为 a 没有真前缀和真后缀,根据规定为 0(可以发现对于任意字符串 π(0)=0 必定成立);

π(1)=1,因为 aa 最长的一对相等的真前后缀为 a,长度为 1;

π(2)=0,因为 aab 没有对应真前缀和真后缀,根据规定为 0;

π(3)=1,因为 aaba 最长的一对相等的真前后缀为 a,长度为 1;

π(4)=2,因为 aabaa 最长的一对相等的真前后缀为 aaa,长度为 2;

π(5)=2,因为 aabaaa 最长的一对相等的真前后缀为 aa,长度为 2;

π(6)=3,因为 aabaaab 最长的一对相等的真前后缀为 aab,长度为 3。

2、如何求前缀函数

首先要先直到几个性质

① π(i) ≤ π(i - 1)+1

该性质很容易理解。π(i) 比 π(i - 1) 多了一个字符串,如果多的这个字符串与 π(i - 1) 的前缀的后一个索引的字符相等,那前缀函数就会多一

 ② 如果 s[i] = s[π(i−1)],那么 π(i) = π(i−1) + 1 

由于 π(i−1) 是真前缀和真后缀相等的长度,那么 s[π(i−1)] 就是 π(i−1) 的真前缀的下一个字符,若该字符与 s[i] 相等,那么 π(i) 必然等于 π(i−1) + 1 

 至此,根据性质②,如果s[i] = s[π(i−1)],那么我们就可以确定 π(i) = π(i−1) + 1 

而如果 s[i] != s[π(i−1)],易知前缀就必须要从 s[0 .. π(i−1) - 1] 中寻找。π(i) <= π(π(i−1) + 1),此时继续判断 s[i] 与 s[π(π(i−1) - 1)],若相等,根据性质②求出 π(i);如不相等,则要继续判断 s[i] 与 s[π(π(π(i−1) - 1))],不断递归。设 j = π(π(π(…)−1)−1),那么我们就要不断递归,直到 s[i] = s[j] 或者 j = 0,π(i) = π(j)

3、解题思路

不断遍历 haystack 字符串和needle 字符串,期间不断与 needle 字符串比较,若遍历完 needle 字符串,说明该字串匹配。若 haystack[i] != needle[j],根据前缀函数的定义,我们只需将 needle 的指针移到 π[j - 1] 即可,然后继续遍历。若无法遍历完 needle 字符串,则说明没有匹配的字串。

4、代码

class Solution {
    public int strStr(String haystack, String needle) {
        int n = haystack.length(), m = needle.length();
        if (m == 0) {
            return 0;
        }
        int[] pi = new int[m]; // 前缀函数,pi[0] = 0
        for (int i = 1, j = 0; i < m; i++) {
        	// 不断递归,直到 j = 0 或 needle[i] == needle[j]
            while (j > 0 && needle.charAt(i) != needle.charAt(j)) {
                j = pi[j - 1];
            }
            // 若needle[i] == needle[j],则pi[i] = pi[j] + 1
            if (needle.charAt(i) == needle.charAt(j)) {
                j++;
            }
            pi[i] = j;
        }
        for (int i = 0, j = 0; i < n; i++) {
        	// 不同,则根据前缀函数跳转needle指针到pi[j - 1]索引
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                j = pi[j - 1];
            }
            // 相同,两个指针都右移一位
            if (haystack.charAt(i) == needle.charAt(j)) {
                j++;
            }
            if (j == m) {
                return i - m + 1;
            }
        }
        return -1;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值