整体思路:
1.目标是从主串 str 中找到子串 sub 开始出现的位置 pos
str : a b c a b c a b c a b c d a b c d e
sub : c a b c d a
找到 pos = 8
2.我们用 i 表示主串 str 的下标,用 j 表示子串 sub 的下标,同时遍历两个字符串,如果 j >= len(sub),则表示子串遍历完了,pos 位置为 i-j (i-len(sub));否则主串已经遍历完了,但没找到子串,pos 的位置为-1
3.主串 str 的下标 i 不可以回退,子串 sub 的下标 j 可以回退
i=5
str : a b c a b d c c
sub : a b c a b c
j=5
此时 i 不动,j 需要回到下标为2的位置,然后比较str[5]和sub[2]
我们可以用一个叫 next 的数组存放 j 的位置,j 根据 next 数组的值进行回退
还是上面那个例子,我们用[]括起来一些信息,如下:
str: ab c[ab]dcc
sub: [ab]c[ab]c
str 中的 [ab] 和 sub 中的第二个 [ab] 一定是匹配的,此时 i=5(指向d),j=5(指向c)
根据i不后退,j后退的目标,j应该退到2的位置(指向c),因此next[5]=2,继续遍历,如果str[i]!=sub[j]则继续回退
4.next数组是遍历子串得到的,通过getNext()实现
next数组的的前两个元素总是-1、0
sub : a b a b a c
next:-1 0 0 1 2 3
对于sub字符串中的a(下标为2),需找到ab中以a开头,b结尾的两个相等的真子串(不存在)的长度,即0
对于sub字符串中的b(下标为3),需找到aba中以a开头,a结尾的两个相等的真子串(下标从0开始的"a"和下标从2开始的"a")的长度,即1
对于sub字符串中的a(下标为4),需找到abab中以a开头,b结尾的两个相等的真子串(下标从0开始的"ab"和下标从2开始的"ab")的长度,即2
对于sub字符串中的c(下标为5),需找到ababa中以a开头,a结尾的两个相等的真子串(下标从0开始的"aba"和下标从2开始的"aba")的长度,即3
总结:next[i]=sub[0:i-1]中,if (找到两个 sub[0]...sub[i-1] 的子串,它们的长度赋给next) else (next[i]=0)
代码实现
//next数组存放j需要回退的位置,数组的前两个元素总是-1、0
func getNext(str string) []int {
//1.数组长度为字符串的长度
next := make([]int,len(str))
if len(str) >= 1{
next[0] = -1
}
if len(str) >= 2 {
next[1] = 0
}
//2.初始化i和k,i为字符串的下标,k=next[i]
i := 1
k := 0
//3.遍历子串
for i < len(str) - 1 {
if k == -1 || str[i] == str[k] {
next[i+1] = k + 1
k = next[i+1]
i++
} else if str[i] != str[k] {
k = next[k]
}
}
return next
}
// str是主串
// sub是字串
// return 字串在主串中的下标
// 假设i为主串的下标,j为字串的下标
// 目标:i不回退,j回退但不一定退到下标为0的位置
func KMP(str string, sub string) int{
strlen, sublen:= len(str),len(sub)
//1.判断字符串长度是否为0
if strlen == 0 || sublen == 0 {
return -1
}
//2.获取next数组
next := getNext(sub)
//3.遍历主串和子串
i, j := 0, 0
for i < strlen && j < sublen {
if j == -1 || str[i] == sub[j] {
i++
j++
} else {
j = next[j] //j可能回退到-1,因此j重置为0(可以用j++),此时i需要往后移动(即i++),因此可以放到上面的if条件里
}
}
//4.子串已经遍历完了,且j==sublen,因此i-j==i-sublen,也就是字串在主串中开始的下标
if j >= sublen {
return i-j
}
return -1
}
func main() {
//1.
//str := "ab"
//str := "ababcabcdabcde"
//str := "abcabcabcabcdabcde"
//getNext(str)
//2.
s := "abcabcabcabcdabcde"
t := "cabcda"
pos := KMP(s, t)
fmt.Println(pos)
}