题目描述
示例
方法一:字串逐一比较 - 线性时间复杂度 O((N-L)L)
思路:
设 needle 的长度为 L, 对 haystack 沿着字符串逐步移动长度为 L 的滑动窗口,将窗口内的子串与 needle 比较。
代码:
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L == 0:
return 0
for start in range(n-L+1):
if haystack[start: start+L] == needle:
return start
return -1
方法二:双指针 - 线性时间复杂度,但可避免比较所有子串
思路:
首先,只有一个子串的第一个字符跟 needle 字符串第一个字符相同的时候才需要比较。
其次,可以一个字符一个字符比较,一旦不匹配了就立刻终止。
如下图所示,比较到最后一位时发现不匹配,这时候开始回溯。需要注意的是,pn指针是移动到 pn - curr_len + 1 的位置,而不是 pn - curr_len 的位置。
这时候再比较一次,就找到了完整匹配的子串,直接返回子串的开始位置 pn - L 。
算法:
- 移动 pn 指针,直到 pn 所指向位置的字符与 needle 字符串第一个字符相等。
- 通过 pn, pL, curr_len 计算匹配长度。
- 如果完全匹配(即 curr_len == L),返回匹配子串的起始坐标(即 pn - L)。
- 如果不完全匹配,回溯。使 pn = pn - curr_len + 1, pL = 0, curr_len = 0。
代码:
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L == 0:
return 0
pn = 0
while pn < n - L + 1:
# 找needle第一个字符在haystack里出现的位置
while pn < n - L + 1 and haystack[pn] != needle[0]:
pn += 1
# 计算最大匹配子串
pL = curr_len = 0
while pL < L and pn < n and haystack[pn] == needle[pL]:
pn += 1
pL += 1
curr_len += 1
# 如果整个needle都被找到了,返回其起始的位置
if curr_len == L:
return pn - L
# 否则,回溯
pn = pn - curr_len + 1
return -1
方法三:Rabin Karp - 常数复杂度 O(N)
思路:
先生成窗口内子串的哈希码,然后再跟 needle 字符串的哈希码作比较。
滚动哈希:常数时间生成哈希码
生成一个长度为 L 的数组的哈希码,需要 O(L)的时间。
- 如何在常数时间生成滑动窗口数组的哈希码?利用滑动窗口的特性,每次滑动都有一个元素进,一个出。
由于只会出现小写的英文字母,因此可以将字符串转化成值 0 ~ 25 的整数数组。例如:
- 窗口此时是 abcd ,则对应的整数数组为 [0, 1, 2, 3]
- 窗口滑动到 bcde , 原数组最左边的 0 被移除,同时最右边新添了 4 。
算法:
- 计算子字符串 haystack[0, L] 和 needle 的哈希值。
- 从起始位置开始遍历:从第一个字符遍历到第 N-L 个字符。
- 根据前一个哈希值计算滚动哈希。
- 如果子字符串哈希值与 needle 字符串哈希值相等,返回滑动窗口起始位置。 - 返回 -1,这时候 haystack 字符串中不存在 needle 字符串。
代码:
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L > n:
return -1
# base value for the rolling hash function
a = 26
# modulus value for the rolling hash function to avoid overflow
modulus = 2**31
# lambda-function to convert character to integer
h_to_int = lambda i : ord(haystack[i]) - ord('a')
needle_to_int = lambda i : ord(needle[i]) - ord('a')
# compute the hash of strings haystack[:L], needle[:L]
h = ref_h = 0
for i in range(L):
h = (h * a + h_to_int(i)) % modulus
ref_h = (ref_h * a + needle_to_int(i)) % modulus
if h == ref_h:
return 0
# const value to be used often : a**L % modulus
aL = pow(a, L, modulus)
for start in range(1, n-L+1):
# compute rolling hash in O(1) time
h = (h * a - h_to_int(start - 1) * aL + h_to_int(start + L -1)) % modulus
if h == ref_h:
return start
return -1