28.实现strStr()-KMP算法
题目
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
示例1
输入:haystack = “hello”, needle = “ll”
输出:2
示例2
输入:haystack = “aaaaa”, needle = “bba”
输出:-1
示例3
输入:haystack = “”, needle = “”
输出:0
解题思路-KMP算法
本题可用KMP算法来解题。则要弄清下面两个大问题,后面还有一些小问题:
- 什么是KMP算法
- 什么是next数组,如何求
理解算法相关问题
- 什么是KMP算法
KMP算法是一个高效的串匹配算法,专门用于串匹配问题,由D.E.Knuth和V.R.Pratt提出,J.H.Morris也几乎同时独立发现了这个算法,因此它被称为KMP算法,其基本思想就是匹配中主串指针不回溯。KMP算法利用比较过的信息,主串指针i不需要回溯,仅将模式串向后滑动一个合适的位置,并从这个位置开始和主串进行比较,这个合适的位置仅与模式串本身有关,与主串无关。那么这个合适的位置在哪?如何求?这里利用next数组存储了这个合适的位置。 - 什么是next数组?如何求?
- next数组含义
next数组(说白了就是前缀表,后面讲解前缀表)存的就是上文指的这个合适的位置,当模式串第j个字符与主串的第i个字符匹配失败时,就在next数组中找模式串第j个字符所对应的k值,即第j个字符匹配失败时,用其下标对应的第k个字符开始与主串继续向后匹配。那么这个k值如何求呢?
想要知道k如何求,就要知道k代表的含义,这里先不说k,听完后面的话就知道k含义了。前文提到前缀表,那么什么是前缀后缀?前缀就是指除最后一个字符以外,字符串的所有头部子串,后缀指除第一个字符以外,字符串的所有尾部子串。k值就代表字符串的前缀和后缀的最长相等前后缀长度。next数组即前缀表,存的就是当模式串的第j个字符匹配失败时,其前j个字符最长相等前后缀的长度。那么为什么要找最长相等前后缀长度呢?因为匹配失败的是后缀后面子串,所以要找到其相等的前缀开始向后匹配。举个例子: 以字符串“ababa”为例:
1.a的最长相等前后缀长度为0
2.ab的最长相等前后缀长度为0
3.aba的最长相等前后缀为a,长度为1
4.abab的最长相等前后缀为ab,长度为2
5.ababa的最长相等前后缀为aba,长度为3
主串为“ababcabcacbab”,模式串为“abcac”用KMP算法匹配如下:
- 求next数组
由上面KMP匹配可看出,当模式串第j个字符匹配失败时,找的是其前面字符(第j-1个字符)的最长相等前后缀长度,其长度k即为所取值。那么我们将next数组向右移动一位(第一位字符所对应的k值取-1),则第j个字符匹配失败时,它自己所对应的k值就是其所取值。next数组整体右移一位,最后一位k值溢出,因为最后一位k值是原next数组最后一位的下一位所用的值,最后一位用不到,故溢出也无所谓。在这里,为什么第一位k值取-1呢?因为当模式串第一个字符匹配失败时,模式串中已没有字符可与主串的当前字符s[i]进行比较,主串当前指针应后移到下一个字符,再和模式串的第一个字符进行比较,这是一种特殊情况。
对人而言,可以很直观的用上面的前后缀长度来求next数组,但是对于计算机而言,有种更高效的求next数组的方法:
上面为当P[j]=P[k]时next算法(即若P[j-1]=P[k-1],则next[j]=k-1+1=k),当P[j]!=P[k]时,k=next[k],继续向前找一个更短的保证匹配的前缀。
KMP算法大致流程
理解了KMP算法及知道如何求next数组后,我们来梳理一下其代码的大致流程:
KMP算法
- 初始两个指针j,i分别为0,指向主串和模式串的第一个字符
- 求主串和模式串的长度,n=len(s),m=len§
- 当j<n and i<m时进入循环体,判断如果i==-1或s[j]==p[i],指针就向后走(i += 1,j += 1)。否则,i=next[i],用其next数组对应的下标为k的字符与当前主串的字符进行比较
- 退出循环,若i==m,则说明找到了匹配子串,返回子串初始位置 j-i,否则,返回 -1 说明没有找到匹配的子串
求next数组
- 初始化模式串下标i为0,k值为-1,模式串长度m=len§,next = [-1] * m
- 当i < m-1时,进入循环体,判断若k==-1或p[i]==p[k],则 i += 1,k += 1,next[i] = k,否则,k=next[k]继续向前找更短的前缀
- 退出循环,则返回next数组
代码
class Solution(object):
def strStr(self, haystack, needle):
"""
:type haystack: str
:type needle: str
:rtype: int
"""
if needle == "":
return 0
# KMP算法
j, i = 0, 0
n, m = len(haystack), len(needle)
next = self.get_pnext(needle)
while j < n and i < m:
if i == -1 or haystack[j] == needle[i]:
j, i = j+1, i+1
else:
i = next[i]
if i == m:
return j - i
return -1
# 求next数组
def get_pnext(self, p):
i, k, m = 0, -1, len(p)
pnext = [-1] * m
while i < m-1:
if k == -1 or p[i] == p[k]:
i, k = i+1, k+1
pnext[i] = k
else:
k = pnext[k]
return pnext
至此,此题解答完毕!