Skip to content

Latest commit

 

History

History
195 lines (137 loc) · 4.75 KB

028._implement_strstr().md

File metadata and controls

195 lines (137 loc) · 4.75 KB

###28. Implement strStr()

题目:

https://leetcode.com/problems/implement-strstr/

难度:

Easy

先来写最符合直觉的写法:

class Solution(object):
    def strStr(self, haystack, needle):
        """
        :type haystack: str
        :type needle: str
        :rtype: int
        """
        i , j = 0, 0
        while i < len(haystack) and j < len(needle):
            if haystack[i] == needle[j]:
                i += 1
                j += 1
            else:
                i = i - j + 1 //退回j步,但是是不匹配的,我们就从下一位开始
                j = 0 //j置为0

        if j == len(needle):
            return i - j
        else:
            return -1

暴力解法二:

class Solution(object):
    def strStr(self, haystack, needle):
        """
        :type haystack: str
        :type needle: str
        :rtype: int
        """
        if not needle:
            return 0
        for i in xrange(len(haystack) - len(needle) + 1):
            if haystack[i] == needle[0]:
                j = 1
                while j < len(needle) and haystack[i+j] == needle[j]:
                    j += 1
                if j == len(needle):
                    return i
        return -1

参考(原创)详解KMP算法

以及 KMP算法的Next数组详解

利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置

至此我们可以大概看出一点端倪,当匹配失败时,j要移动的下一个位置k。存在着这样的性质:最前面的k个字符和j之前的最后k个字符是一样的。

如果用数学公式来表示是这样的

P[0 ~ k-1] == P[j-k ~ j-1]

简单的证明:

因为:

当T[i] != P[j]时

有T[i-j ~ i-1] == P[0 ~ j-1]

由P[0 ~ k-1] == P[j-k ~ j-1]

必然:T[i-k ~ i-1] == P[0 ~ k-1]

好像有点理解了,比如 P 的前j个字符和 T 都匹配(匹配到i):

  • P 中没有重复出现的字符(abcdex),那么很直觉的,我们知道不需要移动i,然后需要把 P 的j移动到0,再次开始对比。

  • P 中有重复出现的字符(abcabx),那么也还是比较直觉的,因为 P 与 T 已经匹配上,所以说明 T 中前面也有重复出现的字符,那么我们也可以跳过重复字符的比较部分,拿着新的字符来比较。

看这两个例子:

			 i		
|a|b|c|d|e|f|g|a|b|...  T
|a|b|c|d|e|x|           P
			 j
			 
			 i		
|a|b|c|d|e|f|g|a|b|...  T
          |a|b|c|d|e|x| P
			 j

这个是没有重复的例子,那么还是比较贴近我们直接的,下次比较我们会直接从 j = 0 再次开始。

			 i		
|a|b|c|a|b|a|a|b|c|...  T
|a|b|c|a|b|x|           P
			 j
			 
			 i		
|a|b|c|a|b|a|a|b|c|...  T
      |a|b|c|a|b|x|     P
			 j

这里有重复,同时我们也知道前面的重复部分已经匹配, 所以我们调到匹配失败但是重复开始的间隔点。

这样也和文中给的例子匹配起来了:

 0        k  j-k      j
| a | b | c | a | b | b
     k-1         j-1

这个k就是每次k需要跳回的部分。

然后到了算法部分:

好,接下来就是重点了,怎么求这个(这些)k呢?因为在P的每一个位置都可能发生不匹配,也就是说我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[j] = k,表示当T[i] != P[j]时,j指针的下一个位置。

虽然还是有点迷茫(关于next数组的部分),但是KMP AC代码如下:

class Solution(object):
    def strStr(self, haystack, needle):
        """
        :type haystack: str
        :type needle: str
        :rtype: int
        """
        def getNext(p):
            next = [0 for _ in range(len(p))]
            next[0] = -1
            j = 0
            k = -1
            while j < len(p) - 1:
                if k == -1 or p[j] == p[k]:
                    j += 1
                    k += 1
                    next[j] = k
                else:
                    k = next[k]

            return next


        def kmp(t, p):
            i = 0
            j = 0
            next = getNext(p)
            while i < len(t) and j < len(p) :
                if j == -1 or t[i] == p[j]: # 当j为-1时,要移动的是i,当然j也要归0
                    i += 1
                    j += 1
                else:
                    # i 不需要回溯了
                    # i = i - j + 1
                    j = next[j] # j回到指定位置

            if j == len(p):
                return i - j
            else:
                return -1

        if needle:
            return kmp(haystack, needle)
        else:
            return 0