Leetcode之Implement strStr()

来源:互联网 发布:都市淘宝捡漏类小说 编辑:程序博客网 时间:2024/05/05 08:06

Implement strStr().

Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

就是说要找到needle在haystack中第一次出现时的index,不存在则返回-1

题目分析:按常规思路,我们会遍历haystack的每个位置,然后从该位置开始和needle进行匹配,该算法的时间复杂度是O(nm)(n、m分别是haystack、needle的长度),空间复杂度为O(1);python代码如下:

class Solution(object):    def strStr(self, haystack, needle):        """        :type haystack: str        :type needle: str        :rtype: int        """                if len(needle) > len(haystack):            return -1                    if len(haystack) == 0 or len(needle) == 0:             return 0                    lenHay = len(haystack)        lenNeedle = len(needle)        startIndex = 0        for i in range(lenHay - lenNeedle + 1):            if haystack[i] == needle[0]:                startIndex = i                if self.checkFromIndex(startIndex, haystack, needle):                    return startIndex        return -1            def checkFromIndex(self, startIndex, haystack, needle):        for i in range(1, len(needle)):            startIndex += 1            if needle[i] != haystack[startIndex]:                return False        return True        

O(mn)的时间复杂度太高,于是又搜了别的解法,才知道这就是传说的KMP问题,好有意思。下面的内容来自http://blog.csdn.net/buaa_shang/article/details/9907183

1.

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

2.

因为B与A不匹配,搜索词再往后移。

3.

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

4.

接着比较字符串和搜索词的下一个字符,还是相同。

5.

直到字符串有一个字符,与搜索词对应的字符不相同为止。

6.

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

7.

一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

8.

怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

9.

已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:

  移动位数 = 已匹配的字符数 - 对应的部分匹配值

因为 6 - 2 等于4,所以将搜索词向后移动4位。

10.

因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。

11.

因为空格与A不匹配,继续后移一位。

12.

逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

13.

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。

14.

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

15.

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

  - "A"的前缀和后缀都为空集,共有元素的长度为0;

  - "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

  - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

  - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

  - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

  - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

  - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

16.

"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

好,移动位数 = 已匹配的字符数 - 对应的部分匹配值,这块可能大家会有疑问,我们来想,部分匹配值是表示needle(haystack与needle当前子串匹配的子串也一样)到当前位置为止的前缀和后缀公共部分的最大长度,所以相当于后缀公共部分我们是可以重新利用的,通过已匹配的字符数-对应的部分匹配值我们就能将needle移动到haystack与needle当前子串匹配的子串的后缀部分。。差不多吧,好吧,其实我也绕晕了。

下一步就是要获得字符串f每一个位置的最大公共长度。这个最大公共长度在算法导论里面被记为next数组。在这里要注意一点,next数组表示的是长度,下标从1开始;但是在遍历原字符串时,下标还是从0开始。假设我们现在已经求得next[1]、next[2]、……next[i],分别表示长度为1到i的字符串的前缀和后缀最大公共长度,现在要求next[i+1]。由上图我们可以看到,如果位置i和位置next[i]处的两个字符相同(下标从零开始),则next[i+1]等于next[i]加1。如果两个位置的字符不相同,我们可以将长度为next[i]的字符串继续分割,获得其最大公共长度next[next[i]],然后再和位置i的字符比较。这是因为长度为next[i]前缀和后缀都可以分割成上部的构造,如果位置next[next[i]]和位置i的字符相同,则next[i+1]就等于next[next[i]]加1。如果不相等,就可以继续分割长度为next[next[i]]的字符串,直到字符串长度为0为止。由此我们可以写出求next数组的代码(java版):

public int[] getNext(String b){int len=b.length();int j=0;int next[]=new int[len+1];//next表示长度为i的字符串前缀和后缀的最长公共部分,从1开始next[0]=next[1]=0;for(int i=1;i<len;i++)//i表示字符串的下标,从0开始{//j在每次循环开始都表示next[i]的值,同时也表示需要比较的下一个位置while(j>0&&b.charAt(i)!=b.charAt(j))j=next[j];if(b.charAt(i)==b.charAt(j))j++;next[i+1]=j;}return next;}
Python版:
def next(self, needle):        length = len(needle)        next = [0] * (length + 1)        next[0] = next[1] = 0        j = 0                for i in range(1, length):            while j > 0 and needle[j] != needle[i]:                j = next[j]            if needle[j] == needle[i]:                j += 1            next[i+1] = j                return next

字符串匹配

计算完成next数组之后,我们就可以利用next数组在字符串O中寻找字符串f的出现位置。匹配的代码和求next数组的代码非常相似,因为匹配的过程和求next数组的过程其实是一样的。假设现在字符串f的前i个位置都和从某个位置开始的字符串O匹配,现在比较第i+1个位置。如果第i+1个位置相同,接着比较第i+2个位置;如果第i+1个位置不同,则出现不匹配,我们依旧要将长度为i的字符串分割,获得其最大公共长度next[i],然后从next[i]继续比较两个字符串。这个过程和求next数组一致,所以可以匹配代码如下(java版):

public void search(String original, String find, int next[]) {int j = 0;for (int i = 0; i < original.length(); i++) {while (j > 0 && original.charAt(i) != find.charAt(j))j = next[j];if (original.charAt(i) == find.charAt(j))j++;if (j == find.length()) {System.out.println("find at position " + (i - j));System.out.println(original.subSequence(i - j + 1, i + 1));j = next[j];}}}
Python版本:
    def strStr(self, haystack, needle):        """        :type haystack: str        :type needle: str        :rtype: int        """                if len(needle) > len(haystack):            return -1                    if len(haystack) == 0 or len(needle) == 0:             return 0                    lenHay = len(haystack)        lenNeedle = len(needle)        startIndex = 0        for i in range(lenHay - lenNeedle + 1):            if haystack[i] == needle[0]:                startIndex = i                if self.checkFromIndex(startIndex, haystack, needle):                    return startIndex        return -1
以上部分内容参考:http://blog.csdn.net/yutianzuijin/article/details/11954939

可知KMP算法的时间复杂度为O(m+n),空间复杂度为O(n)

0 0
原创粉丝点击