kmp算法
kmp基本概念
kmp这个名字有什么含义?
kmp算法的名字只是发明这个算法的科学家的首字母而已
kmp算法有什么作用?
kmp算法主要是用于字符串的匹配上的,我们在比较字符串之间是否匹配的时候,遇到不匹配的字符串不用从头开始比对。
例如这种情况
那么如果我们想要做到简化字符串的匹配的流程,我们就需要知道什么情况下我们可以把我们的匹配进行简化,以及简化了之后我们是和哪个字符进行匹配。
首先什么时候才能进行匹配的简化?
在上一个例子中我们发现:我们在不匹配的字符之前的aa这一个后缀刚好和aabaa这个字符串的前缀aa是相匹配的,这个时候我们就可以进行优化。按照这个思路,我们需要找到每一次我们在每一个字符串不匹配的时候我们在这个字符串之前的最大前后缀匹配串。
由于我们的前后缀都是匹配的,所以下一个匹配的字符就落在了前缀的后面
前缀的长度刚好是2,而我们的需要匹配的下一个字符的索引也是2,所以我们就知道按我们这个逻辑,下一个需要匹配的字符串索引和最大匹配前后缀的长度是相同的。
我们把这个需要“搬运”来进行匹配的下面的这个字符串叫做模式串,我们每当字符串和模式串的字符不相同的时候我们就需要定位到下一个匹配的模式串的索引,这个索引和模式串的长度有关,所以我们需要构造一个数组来装它。我们把它称为前缀表next[ ]
前缀表
我们如何求这个前缀表?
首先我们需要将这个模式串给遍历一遍才能求出所有前缀表的元素。这个就是最外层的循环。
我们定后缀末尾为i,前缀末尾为j。
我们需要三个过程
- 初始化
- 前后缀不同情况的处理
- 前后缀相同的情况的处理
初始化先让next数组的第一个元素定为0,因为第一个元素匹配不成功必然需重新匹配。
然后再让前缀末尾j为0,然后我们进行循环:
int j = 0;
next[0] = 0;
前缀不同的时候:
我们需要匹配到下一个字符去,这个时候我们把我们的 j 重新定位到上一个元素匹配成功的最后位置,但是如果此时这个元素也还是不匹配,我们就需要不断地缩小我们匹配的长度
while(s.charAt(i)!=s.charAt(j)&&j>0){
j = next[j-1];
}
前缀相同的时候
我们就继续让前缀往后即可,后缀再循环中会自己往后
if(s.charAt(i)==s.charAt(j)){
j++;
}
然后让next数组不断地更新
public void findNext(int[] next,String s){
int j = 0;
next[0] = 0;
for(int i = 1;i < s.length();i++){
while(s.charAt(i)!=s.charAt(j)&&j>0){
j = next[j-1];
}
if(s.charAt(i)==s.charAt(j)){
j++;
}
next[i] = j;
}
}
需要注意的是我们在不匹配的时候因为我们下一个匹配的是next数组的上一个元素,所以我们需要保证这个索引不越界。
28. 找出字符串中第一个匹配项的下标
给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
示例 1:
输入:haystack = "sadbutsad", needle = "sad" 输出:0 解释:"sad" 在下标 0 和 6 处匹配。 第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:
输入:haystack = "leetcode", needle = "leeto" 输出:-1 解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。
这题如果使用kmp算法来确定在不匹配的时候下一个匹配的字符串的索引就会很快。
我们在遍历这个haystack字符串的时候也只是需要处理匹配和不匹配的时候。
匹配的时候我们就继续比较下一个字符
不匹配的时候我们就需要索引到下一个我们需要匹配的字符
class Solution {
public int strStr(String haystack, String needle) {
int[] next = new int[needle.length()];//构造next数组
findNext(next,needle);
int j = 0;
for(int i = 0;i < haystack.length();i++){
while(haystack.charAt(i)!=needle.charAt(j)&&j>0){
j = next[j-1];
}
if(haystack.charAt(i)==needle.charAt(j)){
j++;
}
if(j==needle.length()){
return i - needle.length()+1;
}
}
return -1;
}
public void findNext(int[] next,String s){
int j = 0;
next[0] = 0;
for(int i = 1;i < s.length();i++){
while(s.charAt(i)!=s.charAt(j)&&j>0){
j = next[j-1];
}
if(s.charAt(i)==s.charAt(j)){
j++;
}
next[i] = j;
}
}
}
459.重复的子字符串
给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。
示例 2:
输入: s = "aba" 输出: false
示例 3:
输入: s = "abcabcabcabc" 输出: true 解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
首先暴力的算法肯定需要有o(n2)的数量级,所以我们先不考虑。
我们可以考虑的就是我们先获取一个字串,这个字串我们的长度有可能从0到n/2,然后我们去看看这个字符串能不能被子整除,如果能整除,我们就把这个字符串拼接几次,构成一个新的字符串,我们看看这两个字符串是否相等。
class Solution {
public boolean repeatedSubstringPattern(String s) {
int n = s.length();
for(int len = 1;len<=n/2;len++){
if(n%len==0){
String sub = s.substring(0,len);
StringBuilder sb = new StringBuilder();
for(int i = 0;i<n/len;i++){
sb = sb.append(sub);
}
if(sb.toString().equals(s)){
return true;
}
}
}
return false;
}
}