字符串匹配的优化问题十分重要,必须知道Sunday、KMP(nuth–Morris–Pratt algorithm)算法。
题目
实现 strStr() 函数 / 字符串匹配问题。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例
示例 1: 输入: haystack = "hello" , needle = "ll" 输出: 2
示例 2: 输入: haystack = "aaaaa" , needle = "bba" 输出: -1
考察知识点
双指针、字符串
核心思想
这道题虽然简单,但是,字符串匹配是十分基础的问题,相似的字符串匹配算法有 Sunday,KMP,BM,Horspool 。
方法一、暴力遍历循环匹配
直接进行暴力循环
方法二、Sunday
匹配机制非常容易理解:
目标字符串String
模式串 Pattern
当前查询索引 idx (初始为 0)
待匹配字符串 str_cut : String [ idx : idx + len(Pattern) ]
每次匹配都会从 目标字符串中 提取 待匹配字符串与 模式串 进行匹配:
若匹配,则返回当前 idx
不匹配,则查看 待匹配字符串紧接着之后的一位字符 c :
1、若c存在于Pattern中,则 idx = idx + 偏移表 [c]
2、否则,idx = idx + len(pattern)
Repeat Loop 直到 idx + len(pattern) > len(String)
偏移表 的作用是存储每一个在 模式串 中出现的字符,在 模式串 中出现的最右位置到尾部的距离 +1。这个值有什么意义呢? 举个例子
String = "adfaesdsaf" String_length = 9 Pattern = "faesd" Pattern_length = 5 pattern偏移表 = {'a' : 4 , 'd' : 1 , 'e' : 3 , 'f' : 5 , 'ot' : 6 , 's' : 2 }
第一次匹配
idx = 0
str_cut = "adfae" = String[idx, idx+Pattern_length] = String[0: 5]
adfae!= faesd
不匹配,则查看 待匹配字符串 的后一位字符 c='s':
1、若c存在于Pattern中,则 idx = idx + 偏移表 [c] 即 idx = 0 + 2 = 2
2、否则,idx = idx + len(pattern)
解释:虽然'adfa''不匹配'fasf',但是adfae之后的's'就在'faesd'中,偏移量为2,这意味着Pattern中,包含s在内,到结尾处,还有'sd',两个字符。所以,下一个截取起点,从当前位置开始,往后2个位置,截取的一个长度为5的子串,就刚好能涵盖's',且让's'在新子串中的位置和's'在Pattern中的位置一样,都在倒数第二个。
第二次匹配
idx = 2
str_cut = "faesd" = String[idx, idx+Pattern_length] = String[2: 7]
faesd == faesd
匹配成功
大佬题解
方法三、KMP
KMP算法和Sunday类似,也是构造了辅助变量,保存Pattern的相对位移关系来解决字符串匹配问题。
具体过程可以参考 动画:七分钟理解什么是KMP算法
Python版本
基于遍历循环的一个版本
自己完成的版本,直接遍历循环,直接做匹配即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution : def strStr (self, haystack: str , needle: str ) -> int: index = -1 if needle == "" : return 0 if haystack == "" : return index first_char = needle[0 ] needle_len = len (needle) haystack_len = len (haystack) for i,v in enumerate (haystack): if first_char == v and (i + needle_len) <= haystack_len and needle == haystack[i: i+needle_len]: index = i break return index print("leet code accept!!!" ) Input = ["a" , "a" , "" , "hello" , "aaaaa" ] Input1 = ["a" , "" , "" , "ll" , "bba" ] Answer = [0 , 0 , 0 , 2 , -1 ]if __name__ == "__main__" : solution = Solution() for i in range (len (Input)): print("-" *50 ) result = solution.strStr(Input[i], Input1[i]) print(result==Answer[i])
时间复杂度 O(n)
空间复杂度 O(1)
执行用时 :32 ms, 在所有 Python3 提交中击败了82.59%的用户
内存消耗 :13.4 MB, 在所有 Python3 提交中击败了40.08%的用户
Sunday
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution : def strStr (self, haystack: str , needle: str ) -> int: def calShiftMat (st ): dic = {} for i in range (len (st)-1 ,-1 ,-1 ): if not dic.get(st[i]): dic[st[i]] = len (st)-i dic["ot" ] = len (st)+1 return dic needle_len = len (needle) haystack_len = len (haystack) if needle_len > haystack_len:return -1 if needle=="" : return 0 dic = calShiftMat(needle) idx = 0 while idx+needle_len <= haystack_len: str_cut = haystack[idx:idx+needle_len] if str_cut==needle: return idx else : if idx+needle_len >= haystack_len: return -1 cur_c = haystack[idx+needle_len] if dic.get(cur_c): idx += dic[cur_c] else : idx += dic["ot" ] return -1 if idx+needle_len >= haystack_len else idx
时间复杂度:O(nm)或O(n)
执行用时 :32 ms, 在所有 Python3 提交中击败了82.59%的用户
内存消耗 :13.5 MB, 在所有 Python3 提交中击败了40.03%的用户
KMP算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution : def strStr (self, haystack: str , needle: str ) -> int: def get_next (p ): """ 构造子串needle的匹配表, 以 "ABCDABD" 为例 i i i i i i i ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD ABCDABD j j j j j j j """ _next = [0 ] * (len (p)+1 ) _next[0 ] = -1 i, j = 0 , -1 while (i < len (p)): if (j == -1 or p[i] == p[j]): i += 1 j += 1 _next[i] = j else : j = _next[j] return _next def kmp (s, p, _next ): """kmp O(m+n). s以 "BBC ABCDAB ABCDABCDABDE" 为例""" i, j = 0 , 0 while (i < len (s) and j < len (p)): if (j == -1 or s[i] == p[j]): i += 1 j += 1 else : j = _next[j] if j == len (p): return i - j else : return -1 return kmp(haystack, needle, get_next(needle))
返回一个字符串中所有结果的方法 strStrs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class Solution : def strStrs (self, haystack: str , needle: str ) -> list: res = [] if len (haystack) == 0 : return res last_index = 0 is_first = True while haystack: tmp_index = self.strStr(haystack, needle) if tmp_index != -1 : if is_first: res.append(tmp_index) last_index = tmp_index is_first = False else : res.append(tmp_index + last_index + 1 ) last_index = tmp_index + last_index + 1 haystack = haystack[tmp_index+1 : ] else : return res return res def strStr (self, haystack: str , needle: str ) -> int: def calShiftMat (st ): dic = {} for i in range (len (st)-1 ,-1 ,-1 ): if not dic.get(st[i]): dic[st[i]] = len (st)-i dic["ot" ] = len (st)+1 return dic needle_len = len (needle) haystack_len = len (haystack) if needle_len > haystack_len:return -1 if needle=="" : return 0 dic = calShiftMat(needle) idx = 0 while idx+needle_len <= haystack_len: str_cut = haystack[idx:idx+needle_len] if str_cut==needle: return idx else : if idx+needle_len >= haystack_len: return -1 cur_c = haystack[idx+needle_len] if dic.get(cur_c): idx += dic[cur_c] else : idx += dic["ot" ] return -1 if idx+needle_len >= haystack_len else idx