KMP算法

一 、KMP算法

KMP算法指的是字符串模式匹配算法,问题是:在主串T中找到第一次出现完整子串P时的起始位置。该算法是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt同时发现的,以其名字首字母命名

1 next数组

next数组的含义:next[i]第i个字符之前,前缀(长度不能等于i)与后缀相等(长度不能等于i)的最长的长度。
如 abcabcw,对于下表为6的w字符 next[6]为3

前缀长度是否匹配
ac1N
abbc2N
abcabc3Y
abcacabc4N
abcabbcabc5N
abcabcabcabc6N 不合法

2 如何快速求next数组

数组 next 的提取是整个 KMP 算法中最核心的部分,主要是通过消除主串指针的回溯来提高匹配的效率的(记忆化),那么, 消除回溯的呢?
  这种信息就是对于每模式串 t 的每个元素 t j,都存在一个实数 k ,使得模式串 t 开头的 k 个字符(t 0 t 1…t k-1)依次与 t j 前面的 k(t j-k t j-k+1…t j-1,这里第一个字符 t j-k 最多从 t 1 开始,所以 k < j)个字符相同。如果这样的 k 有多个,则取最大的一个。模式串 t 中每个位置 j 的字符都有这种信息,采用 next 数组表示,即 next[ j ]=MAX{ k }。

    private int [] getNext(char [] chars) {
        int n = chars.length;
        if(n==0) {
          return  new int[] {-1};
        }
        int [] next = new int [n];
        next[0] =-1;
        int k =-1;
        int j =0;
        while(j<n-1) {
            if(k==-1||chars[k]==chars[j]) {
                k++;
                j++;
                next[j] = k;
            } else {
              k= next[k];
            }

        }
        return next; 
    }
}

三种情况来讲 next 的求解过程

1 特殊情况

当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。

next[0] =-1;

2 当 t[j] == t[k] 的情况

举个栗子
在这里插入图片描述

观察上图可知,当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",此时的 k 即是相同子串的长度。因为有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1]=k+1。

3 当t[j] != t[k] 的情况

关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];k 回退?我当然知道这是 k 退回.但是它为什么要会退到 next[k] 的位置?看下图
在这里插入图片描述

当 t[j] == t[k] 时,t[j+1] 的最大子串的长度为 k,即 next[j+1] = k+1。但是此时t[j] != t[k] 了,被t[j] != t[k] 打断了,不可能出现next[j+1] =k+1了, 所以就有 next[j+1] < k,那么求 next[j+1] 就等同于求 t[j] 往前小于 k 个的字符(包括t[j],看上图蓝色框框)与 t[k] 前面的字符(绿色框框)的最长重合串,即 t[j-k+1] ~ t[j] 与 t[0] ~ t[k-1] 的最长重合串(这里所说“最长重合串”实不严谨,但你知道是符合 k 的子串就行…),那么就相当于求 next[k](只不过 t[k] 变成了 t[j],但是 next[k] 的值与 t[k] 无关)!!!。所以才有了这句 k = next[k],如果新的一轮循环(这时 k = next[k] ,j 不变)中 t[j] 依然不等于 t[k] ,则说明倒数第二大 t[0~next[k]-1] 也不行,那么 k 会继续被 next[k] 赋值(这就是所谓的 k 回退…),直到找到符合重合的子串或者 k == -1。

再此特别感谢昵称为“sofu6”的博客园主

3 运用next数组实现indexOf

普通算法每次匹配失配i++,j回退到0;
KMP算法是如何加速的?
1 每次匹配失配 ,S串的索引i变为i+j,
2 j变为next[j]。

注意 :极端条件下,next[j]为-1;j回退为0;i++; 与普通算法一样。

4相关题目

28. 实现 strStr()

class Solution {
    public int strStr(String haystack, String needle) {
      if(needle ==null || needle.equals("")){
          return 0;
      }
      int len1 = haystack.length();
      int len2 = needle.length();
      if(len2>len1) {
          return -1;
      }
      int i =0;
      int j =0;

      int [] nexts = getNext(needle.toCharArray());
      while(i<len1 && j<len2) {

        if(haystack.charAt(i) == needle.charAt(j)) {
           i++;
           j++;
        } else {
           if(nexts[j]==-1) {
               i++;
           } else{
               j = nexts[j];
           }
        }
        if(j==len2) return i-len2;
      } 
      return -1;
    }

    private int [] getNext(char [] chars) {
        int n = chars.length;
        if(n==0) {
          return  new int[] {-1};
        }
        int [] next = new int [n];
        next[0] =-1;
        int k =-1;
        int j =0;
        while(j<n-1) {
            if(k==-1||chars[k]==chars[j]) {
                k++;
                j++;
                next[j] = k;
            } else {
              k= next[k];
            }

        }
        return next; 
    }

}

25 214. 最短回文串

给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。

示例 1:

输入: “aacecaaa”
输出: “aaacecaaa”
示例 2:

输入: “abcd”
输出: “dcbabcd”

class Solution {
    public String shortestPalindrome(String s) {
        if(s==null||s.length() ==0) {
            return "";
        }
        String s2 = s+"#" + new StringBuilder(s).reverse().toString();
        return new StringBuilder(s.substring(getNext(s2.toCharArray())+1)).reverse().toString()+s;
    }
    private int getNext(char [] chars) {
        int n = chars.length;
        if(n==0) {
            return -1;
        }
        int [] next = new int [n];
        next[0] =-1;
        int k =-1;
        int j =0;
        while(j<n-1) {
            if(k==-1||chars[k]==chars[j]) {
                k++;
                j++;
                next[j] = k;
            } else {
              k= next[k];
            }

        }
        eturn next[n-1]; 
    } 
}

686. 重复叠加字符串匹配

给定两个字符串 a 和 b,寻找重复叠加字符串 a 的最小次数,使得字符串 b 成为叠加后的字符串 a 的子串,如果不存在则返回 -1。

注意:字符串 “abc” 重复叠加 0 次是 “”,重复叠加 1 次是 “abc”,重复叠加 2 次是 “abcabc”。

示例 1:

输入:a = “abcd”, b = “cdabcdab”
输出:3
解释:a 重复叠加三遍后为 “abcdabcdabcd”, 此时 b 是其子串。
示例 2:

输入:a = “a”, b = “aa”
输出:2
示例 3:

输入:a = “a”, b = “a”
输出:1
示例 4:

输入:a = “abc”, b = “wxyz”
输出:-1

提示:

1 <= a.length <= 104
1 <= b.length <= 104
a 和 b 由小写英文字母组成
通过次数13,746提交次数39,741

参考

1 https://blog.csdn.net/qq_37969433/article/details/82947411
2 https://blog.csdn.net/dark_cy/article/details/88698736

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值