字符串匹配—28.实现strStr()—KMP算法

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数组,如何求
理解算法相关问题
  1. 什么是KMP算法
    KMP算法是一个高效的串匹配算法,专门用于串匹配问题,由D.E.Knuth和V.R.Pratt提出,J.H.Morris也几乎同时独立发现了这个算法,因此它被称为KMP算法,其基本思想就是匹配中主串指针不回溯。KMP算法利用比较过的信息,主串指针i不需要回溯,仅将模式串向后滑动一个合适的位置,并从这个位置开始和主串进行比较,这个合适的位置仅与模式串本身有关,与主串无关。那么这个合适的位置在哪?如何求?这里利用next数组存储了这个合适的位置。
  2. 什么是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算法匹配如下:
    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
        

至此,此题解答完毕!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值