给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba"
输出: -1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-strstr/
字符串haystack 的长度为n,字符串needle 的长度为m。
从上述算法可知,一共会有(n-m+1)个窗口滑动。->这一步的复杂度是O(n)
然后每次窗口滑动,都涉及到两个长度为m的字符串的比较。->这一步的复杂度是O(m)
由于这两部是一个nested loop,所以最终的算法复杂度是O(m*n)。
def violence1():
if haystack == needle:
return 0
n = len(needle)
for i in range(len(haystack) - n + 1):
if haystack[i:i + n] == needle:
return i
return -1
而暴力法的另一种写法:str.index()
def violence2():
return haystack.index(needle) if needle in haystack else -1
Rabin Karp基本思想和暴力破解算法是一样的。也需要一个大小为m的窗口,但是不一样的是,不是直接比较两个长度为m的字符串,而是比较他们的哈希值。
同样的,共会有(n-m+1)个窗口滑动。->这一步的复杂度是O(n)
这个是不变的,但是由于哈希值都是数字,所以两个数字的比较,只需要O(1)。
如何计算哈希值的时间?
在一开始的时候,我们需要计算needle的哈希值,由于needle的长度为m,所以计算needle的哈希值的时间为O(m).
然后每一次移动窗口,都需要对窗口内的字符串计算哈希值,此时这个字符串的长度为m,所以计算它哈希值的时间也为O(m).如果照这样看,算法复杂度还是O(m*n),和上面的暴力破解算法没有任何区别。
但是实际上,计算移动窗口内的哈希值并不需要O(m),在已知前一个窗口的哈希值的情况下,计算当前窗口的哈希值,只需要O(1)的时间复杂度。
现在再来看上面提到的计算字符串哈希值的函数,假设现在窗口的起点在j这个位置,此时窗口内的字符串哈希值为:
那么,当计算下一个窗口的哈希值时,也就是当窗口的起点为j+1时,哈希函数值可由如下方法计算:
所以,这样看来,在计算出第一个窗口的函数值之后,后面的每一个窗口哈希值都可以根据上述公式计算,只需要做一次减法,一次乘法,一次加法。所以之后的每一次哈希值计算都是O(1)的复杂度。
总的时间复杂度就变成了O(n+m)。
KMP算法的时间复杂度同样是(m+n),由于太复杂不容易掌握,所以还是Rabin Karp算法比较好。
注意:hashCode相等时字符串相等的必要条件,即,
当模式长度太大时, 我们使用Horner方法计算模式字符串的散列值:
def cal_hash(s):
BASE = 10 ** 6
res_hash = 0
for i in range(len(s)):
res_hash = (31 * res_hash + ord(s[i])) % BASE
return res_hash
接下来完整的代码:
def rabin_karp():
BASE = 1000000
m = len(needle)
if m == 0:
return 0
# 计算31^m
power = 1
for i in range(m):
power = (power * 31) % BASE
target_code = 0
for i in range(m):
target_code = (target_code * 31 + ord(needle[i])) % BASE
hash_code = 0
for i in range(len(haystack)):
hash_code = (hash_code * 31 + ord(haystack[i])) % BASE
if i < m - 1: # 不够needle长度
continue
if i >= m:
hash_code = (hash_code - ord(haystack[i - m]) * power % BASE)
if hash_code < 0: # hash_code为负数时候要+BASE(一个就可以)
hash_code = haystack + BASE
if hash_code == target_code and haystack[i - m + 1:i + 1] == needle: # 特殊情况hash(abc)
return i - m + 1
return -1
实际上,在python强大的内置函数下,并没有快多少,,,只是熟悉一下hashCode的计算方法,以后用到更合适的地方。
————————————————
参考:
https://blog.csdn.net/lucylove3943/article/details/83491416
http://blog.chinaunix.net/uid-26548237-id-3968132.html
https://www.cnblogs.com/golove/p/3234673.html
https://blog.csdn.net/zhongkeli/article/details/8793684