【leetcode学习笔记】KMP算法

一、背景

1.1 问题背景-字符串匹配

给定一个长字符串src,比如一篇文章;再给定一个搜索的目标target,比如一个词语或者一个短句。要找到src中找到子串target出现的第一个位置,如果没有则返回-1。

https://leetcode.cn/problems/implement-strstr/

1.2 暴力解法

class Solution {
    public int strStr(String src, String target) {
        int m = src.length();
        int n = target.length();

        if(n > m){return -1;}
        int flag;
        for(int i = 0; i <= m-n; i++){
            flag = 0;
            for(int j = 0; j < n; j++){
                if(src.charAt(i+j) != target.charAt(j)){
                    flag = 1;
                    break;
                }
            }
            if(flag == 0){return i;}
        }
        return -1;
    }
}

从src的每一个字符开始往后逐位比对,匹配失败则从下一位开始从头匹配,匹配成功就返回结果。显然,时间复杂度是O(mn)

我们以ABABABC中查找ABABC为例,暴力匹配的第一轮结果是:

(ABAB)ABC

(ABAB)C

这里我们发现已经匹配成功的子串中,有公共前后缀AB。那么实际上,中途的很多匹配过程可以跳过,我们只需要从

AB(AB)ABC

     (AB)ABC

状态继续匹配即可。假设我们有两个指针i和j,分别指向src和target要比对的元素的位置。那么暴力解法中,i的位置会经常发生回退,从而造成了时间的浪费。而如果把公共前后缀的信息考虑进来,可以大幅提升算法时间复杂度。

二、KMP算法

2.1 src和target如何匹配

第一步:产出和target长度相同的next数组,next[i]表示target[0, ..., i]的最长公共前后缀(不包括自身)。

第二步:初始化遍历指针i=0和j=0,循环执行第三步。遍历过程中如果j已超出target范围,那么匹配成功,返回对应位置。如果i已超出src范围,说明target不存在,返回-1。

第三步:比较src[i]和target[j]。如果两者相等,那么i++,j++,继续执行第三步。如果两者不相等,那么看next[i]。如果next[i] > 0,j回退到next[i];如果next[i] = 0,i++,j=0。length变成next[i]。

2.2 如何构建next数组

首先next[0] = 0是必然事件。假设next[i-1]=k,那么就意味着,target[0, ..., k-1] == target[i-k, ..., i-1]。如果target[i] = target[k],那么target[0, ..., k] == target[i-k, ..., i]一定是最长公共前后缀,next[i]=k+1。

如果target[i]不等于target[k]怎么办呢?此时我们要去看next[k-1],设为t。它表明了target[0, ..., k-1]的最长公共前后缀是target[0, ..., t-1] == target[k-t, k-1],又因为target[0, ..., k-1] == target[i-k, ..., i-1],那么target[0, ..., t-1] == target[i-t, ..., i-1]。于是我们令k=t(即next[k-1])。继续比较target[i]和target[k],重复上述过程即可。

2.3 最终解

class Solution {
    public int strStr(String src, String target) {
        int[] next = buildNext(target);
        int m = src.length();
        int n = target.length();

        int i = 0;
        int j = 0;
        while(i < m){
            if(src.charAt(i) == target.charAt(j)){
                i++; j++;
            }
            else if(j == 0){i++;}
            else{j = next[j-1];}
            if(j == n){return i - j;}
        }

        return -1;
    }

    public int[] buildNext(String target){
        int length = target.length();
        int[] res = new int[length];
        res[0] = 0;

        int i = 1;
        int now = 0;
        while(i < length){
            if(target.charAt(i) == target.charAt(now)){
                now++;
                res[i] = now;
                i++;
            }
            else if(now > 0){
                now = res[now-1];
            }
            else{
                res[i] = 0;
                i++;
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值