2023.6.7
KMP给我人脑cpu干烧了┭┮﹏┭┮
第一阶段:最长相等前后缀的引入给暴力解法带来的改善
暴力解法:
needle每次向前推进一位,然后判断是否与haystack中的相应字串对应,如图所示
当已知needle中不配对位 前面字串 的 最长相等前后缀的长度时,就不需要只向前推动一位,而是直接向前推进x1-x2位,其中x1是(不配对位前面字串的长度),x2是(不配对位前面字串的最长相等前后缀的长度):如图所示
这就是为什么KMP算法比暴力配对算法快的原因,KMP算法每次推进多少是根据不配对位前面字串的信息获取到的,而不是无脑向前推进一位,因此能够更快地成功配对。
tips:关于x2不配对位前面字串的最长相等前后缀的长度怎么算,参见随想录。
那么为什么要推进x1-x2位?要说明这个问题,只需要想明白推进小于x1-x2位时是不可能成功配对的。可以自己假设以下,上图中,如果needle向前推进1位以后,成功配对了,这将意味着aabaa这个字串必定存在着长度为4的相等前后缀,这显然与x2=2相矛盾。同理如果needle向前推进2位以后,成功配对了,这将意味着aabaa这个字串必定存在着长度为3的相等前后缀,这显然也与x2=2相矛盾。只有当向前推进3位时,才有成功配对的可能(此时needle中最长前缀与haystack中的最长后缀一定能够对应上,是否能够成功配对取决于后续能否继续全部对应上)。
第二阶段:想明白如下问题,当已知一个字符串及其最长相等前后缀的长度,在当前字符串后面再添加一个字符,如何求新字符串的最长相等前后缀的长度。(为求next数组做准备)
定理一:如过一个s字符串的最长相等前后缀的长度为n,那么在当前字符串后面删除一个字符,新字符串new_s的最长相等前后缀的长度大于等于n-1,证明如图所示:
例子中可以看出原本最长相等前后缀的长度为2,删尾后为至少为1,不可能小于1,还存在其他的串s删尾后最长相等前后缀反而增大,可以自己举例。
证明这个定理,首先要理解为什么最长相等前后缀的长度小于等于n+1
定理二:如过一个s字符串的最长相等前后缀的长度为n,那么在当前字符串后面再添加一个字符,新字符串new_s的最长相等前后缀的长度小于等于n+1,当且仅当新加的字符等于s[n]时,等号成立。
证明这个定理,首先要理解为什么新字符串new_s最长相等前后缀的长度小于等于n+1,这个根据定理一,如果新字符串new_s最长相等前后缀的长度大于等于n+2,那么去掉末尾字符后,s最长相等前后缀的长度大于等于n+1,与条件中s字符串的最长相等前后缀的长度为n相矛盾,所以新字符串new_s最长相等前后缀的长度小于等于n+1。其次当且仅当新加的字符等于s[n]时,等号成立这个很好理解。
那么新的问题来了,如果新加的字符不等于s[n],该如何计算new_s的最长相等前后缀的长度:
这就跟求next的代码对上了。
因此代码求每一个字符串的最长相等前后缀的长度时是由上一个字串的最长相等前后缀的长度推导出来的。
class Solution:def strStr(self, haystack: str, needle: str) -> int:if not needle:return 0keeper = [0] * len(needle)self.getkeeper(keeper, needle)j = 0for i in range(0, len(haystack)):# print(j)while (j>0) and (haystack[i] != needle[j]):j = keeper[j-1]if haystack[i] == needle[j]:j += 1if j == len(needle):return i - len(needle) + 1return -1def getkeeper(self, keeper, needle):j = 0keeper[0] = 0for i in range(1, len(needle)):while (j>0) and (needle[i] != needle[j]):j = keeper[j-1]if needle[i] == needle[j]:j = j+1keeper[i] = j