KMP笔记

KMP

推荐视频

kmp 哔哩哔哩

例题

28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

算法

先假设有一个待搜索字符串t,和一个模式串p

传统:暴力匹配

暴力匹配以i = 0i = n - m为开头依次比较p串的每一位是否相同,全相同则匹配成功

KMP

最大公共前后缀

需要注意的是,这里的前后缀都不包括字符串本身

kelvin这一字符串为例

前缀: {k, ke, kel, kelv, kelvi}

后缀: {n, in, vin, lvin, elvin}

我们使用int d[]来保存最大公共前后缀,d[j]表示p[0..j]的最大公共前后缀

最大公共前后缀可以带来什么好处

KMP算法通过计算p串已匹配部分的==最大公共前后缀==,以减少已经符合规则的p串部分(在遇到一个与t串不匹配的字符时,不是抛弃所有已匹配的字符,而是通过D得到p串已匹配字符中可以利用的部分(最大公共前后缀))

			i
			↓
t :  ...aaaaaab...
p : 	aaaab
			↑
			j

如上面的例子,t[i]p[j]无法匹配。

这时p[0..j-1]是匹配成功,且已匹配成功的最大公共前后缀为d[j-1],如何计算d我会在后文给出,这里我们先通过肉眼观察到d[j-1]为3。

由此可知,t串不匹配字符的前3个字符是与p串的前3个字符相同的,所以我们可以直接对t串的第i个字符与p[3]进行匹配,而无需从头对p串进行匹配

如何计算最大公共前后缀

由于最大公共前后缀不包括字符串本身,所以d[0] = 0

这里其实与上小节思路相同,也是在某一字符匹配失败时,尝试使用已匹配成功的最大公共前后缀来减少比较次数。

p : aaaab
p :  aaaab
代码
// LC-28
class Solution {
    public int strStr(String haystack, String needle) {
        int n = needle.length(), m = haystack.length();
        if (n == m) {
            return haystack.equals(needle) ? 0 : -1;
        } else if (n > m) {
            return -1;
        }

        // d[x]表示,needle[0] ~ needle[x] 区间的最大公共前后缀
        // 特别地:前缀不包括needle[x],后缀不包括neddle[0];
        int[] d = new int[n];
        // 所以当只有一个字符时,最大公共前后缀为0
        d[0] = 0;

        for (int i = 1, j = 0; i < n; i++) { // 错开一位
            while (needle.charAt(i) != needle.charAt(j) && j > 0) { // 第j位发生了不匹配
                // 说明前needle[0 .. j - 1]位是匹配的
                // 获取前j - 1位的最大公共前后缀
                j = d[j - 1]; 
            }

            if (needle.charAt(i) == needle.charAt(j)) {
                j++;
            }
            d[i] = j;
        }

        for (int i = 0, j = 0; i < m; i++) {
            while (haystack.charAt(i) != needle.charAt(j) && j > 0) {
                j = d[j - 1];
            }

            if (haystack.charAt(i) == needle.charAt(j)) {
                j++;
            }

            if (j == n) {
                return i - n + 1;
            }
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值