面试时碰到一个算法题,判断一个字符串是不是一段字符的子串,当时不加思索的就写出两段for循环,伪代码如下
#src为源串 pat为子串
i=0
while(i<len(src) && len(src) >== i + len(pat)){
j=0
while(j<len(pat) && i+j<len(src) && src[i+j]==pat[j]){
j++
}
if(j==len(pat){
break;
}
i++
}
若源串长度为n,匹配串长度为m,则最坏情况下时间复杂度为O(m*(n-m+1))
下面就我理解的KMP算法进行记录
KMP算法关键是对匹配字符串进行一些处理,首先分配一个跟匹配字符同样长度的最大前缀匹配的长度。
比如下面的字符串对应的最大前缀匹配长度
A | B | C | D | E |
0 | 0 | 0 | 0 | 0 |
A | A | A | A | B |
0 | 1 | 2 | 3 | 0 |
A | A | B | A | A |
0 | 1 | 0 | 1 | 2 |
A | B | A | A | A |
0 | 0 | 1 | 1 | 1 |
下面使用GO语言实现获取最大匹配前缀长度
func getLpsArray(pat string) []int {
ret := make([]int, len(pat))
lps := 0 //最大前缀匹配长度
//首字母匹配长度为0
for i := 1; i < len(pat); i++ {
if pat[i] == pat[lps] {
lps++
ret[i] = lps
} else {
if lps != 0 {
lps = ret[lps-1]
i--
}
}
}
//fmt.Println(ret)
return ret
}
下面以AABAA阐述这段代码
首字母lps全部为0 即 lps=0 ret[0]=0
循环i=1时 lps=0 pat[1]=‘A’与pat[0]='A'相等,最大匹配长度加1 lps=1 当前字符的最大匹配前缀为1 ret[1]=1
循环i=2时 lps=1 pat[2]='B'与pat[1]='A'不符,需继续匹配最大前缀前一个字符,此时lps不为0,更新lps=ret[lps-1],lps=ret[0]=0, 继续与pat[2]进行匹配
循环i=2 lps=0 pat[2]='B'与pat[0]='A'不符,此时lps=0,即ret[2]=0
循环i=3时 lps=0 pat[3]='A'与pat[0]='A'相等,最大匹配长度加1 lps=1 当前字符的最大匹配前缀为1 ret[3]=1
循环i=4时 lps=1 pat[3]='A'与pat[0]='A'相等,最大匹配长度加1 lps=2 当前字符的最大匹配前缀为1 ret[4]=2
下面实现KMP查找
//src为源串 pat匹配串 返回值为匹配串在源串中的索引位置
func KmpSearch(src, pat string) int {
if len(pat) < 1 || len(src) < len(pat) {
return -1
}
iRet := -1
for i, j, lps := 0, 0, getLpsArray(pat); i < len(src); i++ {
if src[i] == pat[j] {
j++
if j == len(pat) {
//找到相匹配的
iRet = i - len(pat) + 1
break
}
} else {
if j > 0 {
j = lps[j-1]
i--
}
}
}
return iRet
}
以src="AAAABAA" pat="AABAA"阐述代码
构建lps=[0,1,0,1,2]
i=0时 j=0,src[0]=pat[0]='A', i,j自增 i=1,j=1
i=1时 j=1,src[1]=pat[1]='A', i,j自增 i=2,j=2
i=2时 j=2,src[2]='A' pat[2]='B"不相等,此时j=2,更新j为上一次匹配j=1时lps[1]对应的值1,即j=1
i=2 j=1,src[2]=pat[1]='A', i,j自增 i=3,j=2
i=3时 j=2,src[3]='A' pat[2]='B"不相等,此时j=2,更新j为上一次匹配j=1时lps[1]对应的值1,即j=1
i=3 j=1,src[3]=pat[1]='A', i,j自增 i=4,j=2
i=4 j=1,src[4]=pat[2]='B', i,j自增 i=5,j=3
i=5 j=3,src[5]=pat[3]='A', i,j自增 i=6,j=4
i=6 j=4,src[6]=pat[4]='A' 此时找到匹配串退出循环