前言
现在才想起大二算法课的良苦用心,秋招还得再捡起来。
KMP 一直是老大难问题,实际上就是一个 next 数组求法。
Next 数组
为什么要有这个东西? 方便快速跳过相同前缀,不用匹配到一半发现不对又回去从头重新匹配。
所以 next
是指,匹配失败了,下一个找前面的谁。
比如下面两个字符串比较:
aafaabaafaab
aabaaf
求出 aabaaf
的 最大匹配前后缀数组:[0, 1, 0, 1, 2, 0]
全部 -1 得到 next
数组:[-1, 0, -1, -1, 1, -1]
涉及到一个求 next
数组的算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private int[] next(String s){ int[] next = new int[s.length()];
int j = -1; next[0] = j;
for(int i = 1; i < next.length; i++){ while(j >= 0 && s.charAt(i) != s.charAt(j + 1)){ j = next[j]; } if(s.charAt(i) == s.charAt(j + 1)){ j++; } next[i] = j; }
return next; }
|
匹配
思想继承了 next 数组,甚至连代码都差不多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public int strStr(String haystack, String needle) { if(needle.length() == 0){ return 0; } int[] next = next(needle);
int j = -1; for(int i = 0; i < haystack.length(); i++){ while(j >= 0 && haystack.charAt(i) != needle.charAt(j + 1)){ j = next[j]; } if(haystack.charAt(i) == needle.charAt(j + 1)){ j++; }
if(j == needle.length() - 1){ return i - j; } }
return -1; }
|
拓展
重复的子字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。 示例 2:
输入: s = "aba" 输出: false 示例 3:
输入: s = "abcabcabcabc" 输出: true 解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
|
next 数组还有更巧妙的用法:
比如 abcabcabcabc
:
next = [-1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8]
不用倒过来看,就是按照顺序排列的
可以看到 next 数组在初始字符串之后开始呈现规律递增
最终也是停留在了 next.length() - sub.length() - 1
不难发现,想要最后一个子串全匹配, sub.length()
就得把 next.length()
整除
因此,判断是否整除成为了检查条件。
1 2 3 4 5 6 7 8 9
| public boolean repeatedSubstringPattern(String s) { int[] next = next(s);
int rep = next.length - (next[next.length - 1] + 1); if(rep != next.length && next.length % rep == 0){ return true; } return false; }
|