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)
- leetcode之Implement strStr()
- LeetCode之Implement strStr()
- LeetCode之Implement strStr()
- 【Leetcode】之Implement strStr()
- LeetCode 之 Implement strStr()
- Leetcode之Implement strStr()
- LeetCode 之 Implement strStr()
- Leetcode之Implement strStr() 问题
- LeetCode算法题之Implement strStr()
- LeetCode 之 Implement strStr() — C 实现
- leetcode之路028 Implement strStr()
- LeetCode进阶之路(Implement strStr())
- LeetCode: Implement strStr()
- [Leetcode] Implement strstr()
- Leetcode: Implement strStr
- [Leetcode] Implement strStr()
- LeetCode Implement strStr()
- 【leetcode】Implement strStr()
- 【PHP入门篇】 8.PHP开发工具之Zend Studio常用功能--慕课网【学习总结】
- C++学习笔记40——重载作符之算术操作符与关系操作符
- iOS - deprecated属性
- Java反射例子
- 最全面的常用正则表达式大全
- Leetcode之Implement strStr()
- jquery实现模拟点击跳转 $.trigger
- textarea 换行显示处理
- 图片临时路径
- IOS开发系列——UIView专题之五:常用开发技巧篇
- DuiLib改底层支持Icon图片显示
- java socket一对多通信编程
- 解决远程连接MYSQL时无法连接的问题(Access denied for...)
- SPRING