题目描述:
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
思路分析:
在进入主要流程之前应该进行非法行为断定:
- 因为在题目中已经表示了两个字符串不会为空,所以不需要判断是否为空的情况
- 如果模式串比目标串要长,则不可能有符合条件的下标,于是这种情况非法,其余情况正常继续。
找一个模式串在目标串中的起始位置,典型的KMP算法。有两种实现方法:暴力循环和KMP方法。他们的区别在于暴力法的复杂度是O(n^2)和O(m+n)。
暴力循环法:
就是依次从目标串的第一个位置不断开始找,如果达到模式串的长度时的字符串仍然相同,那就说明查找成功。否则从下一个开始的位置进行查找,具体实现如下,重点关注一下两个循环的指针的设计方案。用模式串的长度进行指针挪动可以减少一次循环。
点击查看代码
func strStr(haystack string, needle string) int {
if len(needle) == 0 {
return 0
}
var i, j int
// i不需要到len-1
for i = 0; i < len(haystack)-len(needle)+1; i++ {
for j = 0; j < len(needle); j++ {
if haystack[i+j] != needle[j] {
break
}
}
// 判断字符串长度是否相等
if len(needle) == j {
return i
}
}
return -1
}
KMP算法
前缀: 包含第一个字符的子串
KMP算法的核心就是利用之前的匹配过程的信息(最长公共前缀。因为他在匹配的过程中记录最长公共子串的长度,他相当于是对称的===:前面几个长度的字符和后面几个字符的长度是相同的,并且如果next数组(记录当前公共子串的长度)的值不为0,就说明到目前为止模式串和目标串的元素是有相同的的存在的。
即有三种情况:
p []int //寻找LPS的string
next []int //存储LPS的数组
length int //当前最长的LPS长度
- 当前字符串相同的,匹配成功。length++,向后移动
- 匹配失败:当前字符和之前的字符也没有能够组成的LPS,这里需要递归寻找,具体操作方法为:
else if length!=0 {
//只有这个判断条件没有发生i++,即找到相同的为止,要么重新找。这就是递归的体现。
length = lps[length-1]
给一个具体的例子:(来自知乎@凉拌炒鸡蛋)
当找到下标13的c的时候,发生不匹配现象,但是之前串的length = 5,如果从头开始匹配复杂度就又上去了,所以依然要利用之前的匹配信息,将length-1直到为0看是否匹配。length-1的效果:其实就是不断缩短前缀的长度,看到最新不匹配的位置是否能通过减少字符的方法匹配上,他减少2个字符就匹配上了。 - 实在不匹配就置为0,向后继续匹配。
代码:
点击查看代码
func computeLPSArray(pat string) []int {
lps:=make([]int,len(pat))
length:= 0
//从位置1开始遍历,找最长前缀长度
for i := 1; i < len(pat);{
if pat[i]==pat[length] {
length++
lps[i] = length
i++
}else if length!=0 {
//只有这个判断条件没有发生i++,即找到相同的为止,要么重新找。这就是递归的体现。
length = lps[length-1]
}else {
lps[i] = 0
i++
}
}
return lps
}