# KMP 模板

学习自《程序员代码面试指南》,作者左程云

最经典的字符串匹配问题,力扣链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/submissions/

要对查找串建立 next 数组,也就是前后缀匹配数组

private int[] getNext(char[] m) {
    int n = m.length;
    if(n <= 1) return new int[]{-1};
    
    int[] next = new int[n];
    next[0] = -1; next[1] = 0;
    int pos = 2, cn = 0;
    while(pos < n) {
        if(m[pos-1] == m[cn]) next[pos++] = ++cn;
        else if(cn > 0) cn = next[cn];
        else next[pos++] = 0;
    }
    return next;
}

然后通过 next 数组进行快速匹配字符串

public int match(Sting s, String m) {
    int sn = s.length(), mn = m.length();
    if(sn < mn) return -1;
    
    char[] arrS = s.toCharArray(), arrM = m.toCharArray();
    int[] next = getNext(arrM);
    int i = 0, j = 0;
    while(i < sn && j < mn) {
        if(arrS[i] == arrM[j]) {
            i++;
            j++;
        } else if(next[j] == -1) i++;
        else j = next[j];
    }
    
    return j == mn ? i - j : -1;
}

# 其他写法

在此我们看一下力扣官方对于 kmp 的写法,这里需要提的是,上一个方法算的 next 数组的 next[i] 表示从 0~i-1 的前缀与后缀相同的长度,我们定义的 next 长度只有 n ,所以它其实不能得到从 0~n-1 的前缀与后缀相同的长度。其实方法选一个自己喜欢的就好了,我个人是比较喜欢上面的方法,因为先学的那个。

int[] next = new int[mn]; // 从 1 开始,next [0] 默认为 0
for (int i = 1, j = 0; i < mn; i++) {
    //m [i] 就是当前的字符,在上一个方法中,是当前字符的前一个字符
    while (j > 0 && m[i] != m[j]) j = next[j - 1];
    if (m[i] == m[j]) j++;
    next[i] = j;
}

# 扩展题

kmp 算法需要求出一个 next 数组,这个数组可以解决一些衍生问题:

力扣 214. 最短回文子串,难度【困难】:https://leetcode.cn/problems/shortest-palindrome/

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

示例1
输入:s = "aacecaaa"
输出:"aaacecaaa"
示例2
输入:s = "abcd"
输出:"dcbabcd"

该题的一种解法就需要用到 next 数组,因为这题本质就是找到 s 的最长前缀回文字符串,假设 s 可以表示为 【】> 【】表示回文串,> 表示不回文),那么将这个 s 反转一下就成了 <【】 ,将两个字符串拼接一下: 【】>*<【】 ,这样的字符串肯定回文,但不是最短的,我们只是借助这个字符串形成的 next 数组判断最长前缀回文字符串。

public String shortestPalindrome(String s) {
	StringBuilder sb = new StringBuilder();
    sb.append(s);
    sb.append("*"); // 如果 s 是 aa 这样的,不加 * 就是 aaaa,那么 next 的结果就是 4,需要加 * 使其为 aa*aa
	sb.append(new StringBuilder(s).reverse());
    
    int[] next = new int[sb.length()+1];
    next[0] = -1; next[1] = 0;
    int pos = 2, cn = 0;
    while(pos <= sb.length()) {
        if(sb.charAt(pos-1) == sb.charAt(cn)) next[pos++] = ++cn;
        else if(cn > 0) cn = next[cn]; 
    	else next[pos++] = 0;
    }
    
    return new StringBuilder(s.substring(next[sb.length()], s.length())).reverse() + s;
}