字符串算法(String Algorithms)完全详解 - 从暴力到KMP¶
重要性:⭐⭐⭐⭐⭐ 难度:⭐⭐⭐⭐ 学习时间:7-10天 大厂面试频率:极高(字节、腾讯、阿里必考) 前置知识:数组、循环、基础算法
📚 目录¶
字符串基础¶
为什么字符串算法重要?¶
✅ 应用极其广泛: - 搜索引擎(Google、百度) - 文本编辑器(查找、替换) - 生物信息(DNA序列匹配) - 数据压缩 - 编译器(词法分析) - 网络安全(入侵检测)
✅ 面试必考: - 字符串匹配(KMP必问!) - 字符串操作(反转、分割) - 正则表达式 - 字符串DP(编辑距离)
字符串的内存表示¶
# Python中的字符串
s = "Hello"
# 内存结构(简化)
# 每个字符占1-4字节(取决于编码)
# ASCII: 1字节/字符
# Unicode: 2-4字节/字符
s = "Hello"
# 内存:
# [H][e][l][l][o]
# ↑ ↑ ↑ ↑ ↑
# 0 1 2 3 4 (索引)
# 字符串长度
len(s) # 5
# 访问字符
s[0] # 'H'
s[-1] # 'o' (最后一个)
# 切片
s[1:4] # 'ell'
s[::2] # 'Hlo' (每隔一个)
字符串匹配问题定义¶
问题:在文本串T中查找模式串P的所有出现位置
示例:
文本串 T = "ABABDABACDABABCABAB"
模式串 P = "ABABCABAB"
查找:P在T中出现的位置?
位置7: "ABABCABAB"
↑
匹配!
T: ABABDABACDABABCABAB
↑
P第一次出现
暴力匹配算法¶
算法思想¶
最朴素的方法:从T的每个位置开始,尝试匹配P
T: "ABABABC"
P: "ABA"
步骤:
位置0: ABA vs ABA ✓ 匹配!
位置1: BAB vs ABA ✗ 不匹配
位置2: ABA vs ABA ✓ 匹配!
位置3: BAB vs ABA ✗ 不匹配
位置4: ABC vs ABA ✗ 不匹配
结果:位置0和位置2匹配
代码实现¶
def brute_force_match(text, pattern):
"""
暴力字符串匹配
时间:O(mn), m=len(text), n=len(pattern)
空间:O(1)
"""
m, n = len(text), len(pattern)
if n == 0:
return []
if m < n:
return []
positions = []
print(f"文本串: {text}")
print(f"模式串: {pattern}")
print(f"\n匹配过程:")
# 遍历text的每个位置
for i in range(m - n + 1):
print(f"\n位置{i}: ", end="")
# 检查从i开始是否匹配
match = True
for j in range(n):
print(f"{text[i+j]}", end="")
if text[i+j] != pattern[j]:
print(" ✗", end="")
match = False
break
if match:
print(" ✓ 匹配!")
positions.append(i)
else:
print()
print(f"\n匹配位置: {positions}")
return positions
# 测试
print("=" * 60)
print("暴力匹配算法")
print("=" * 60)
text = "ABABABC"
pattern = "ABA"
positions = brute_force_match(text, pattern)
# 输出:
# ============================================================
# 暴力匹配算法
# ============================================================
# 文本串: ABABABC
# 模式串: ABA
#
# 匹配过程:
#
# 位置0: ABA ✓ 匹配!
#
# 位置1: BAB ✗
#
# 位置2: ABA ✓ 匹配!
#
# 位置3: BAB ✗
#
# 位置4: ABC ✗
#
# 匹配位置: [0, 2]
时间复杂度分析¶
最坏情况:
T = "AAAAAA...AAAAAB" (m个A)
P = "AAA...AAAB" (n个A)
每个位置都要比较n次
时间:O(mn)
最好情况:
T = "ABCDEF..."
P = "XYZ"
第一次就不匹配
时间:O(m)
平均情况:
O(mn)
缺点: - 效率低(O(mn)) - 重复比较 - 没有利用失败信息
改进方向: - 利用已经匹配的信息 - 跳过不可能匹配的位置 - → KMP算法!
KMP算法¶
什么是KMP?¶
KMP(Knuth-Morris-Pratt)算法是一种改进的字符串匹配算法,利用已经匹配过的信息,避免重复比较。
核心思想¶
关键问题:当匹配失败时,模式串应该向右移动多少?
暴力算法:每次只移动1位(低效)
KMP算法:根据部分匹配表(next数组),智能跳过不可能匹配的位置
图解对比¶
暴力算法:
T: A B A B A B C A
P: A B A B D
匹配到D时失败:
T: A B A B A B C A
P: A B A B D
↑
移动1位,重新比较
KMP算法:
匹配失败后,查next数组
T: A B A B A B C A
P: A B A B D
↑
直接跳到这个位置!
next数组(前缀表)¶
定义:next[i] = P[0...i]的最长相同真前缀和真后缀的长度
示例:P = "ABABCABAB"
索引: 0 1 2 3 4 5 6 7 8
字符: A B A B C A B A B
next: 0 0 1 2 0 1 2 3 4
解释:
i=0: "A" 无前缀后缀 → next[0]=0
i=1: "AB" 无相同前后缀 → next[1]=0
i=2: "ABA" 前缀"A"=后缀"A" → next[2]=1
i=3: "ABAB" 前缀"AB"=后缀"AB" → next[3]=2
i=4: "ABABC" 无相同前后缀 → next[4]=0
i=5: "ABABCA" 前缀"A"=后缀"A" → next[5]=1
i=6: "ABABCAB" 前缀"AB"=后缀"AB" → next[6]=2
i=7: "ABABCABA" 前缀"ABA"=后缀"ABA" → next[7]=3
i=8: "ABABCABAB" 前缀"ABAB"=后缀"ABAB" → next[8]=4
计算next数组¶
def compute_next(pattern):
"""
计算KMP的next数组
时间:O(m), m=len(pattern)
"""
m = len(pattern)
next_arr = [0] * m
print("计算next数组:")
print(f"模式串: {pattern}")
print(f"索引: {' '.join(str(i).rjust(3) for i in range(m))}")
print(f"字符: {' '.join(pattern)}")
print()
# j表示前缀长度
j = 0
for i in range(1, m):
# 当不匹配时,回退到前一个位置
while j > 0 and pattern[i] != pattern[j]:
print(f" next[{i}]: pattern[{i}]({pattern[i]}) != pattern[{j}]({pattern[j]}), j从{j}回退到next[{j-1}]={next_arr[j-1]}")
j = next_arr[j-1]
# 当匹配时,前缀长度+1
if pattern[i] == pattern[j]:
j += 1
print(f" next[{i}]: pattern[{i}]({pattern[i]}) == pattern[{j-1}]({pattern[j-1]}), j={j}")
next_arr[i] = j
print(f" → next[{i}] = {j}\n")
print(f"next数组: {next_arr}")
return next_arr
# 测试
print("=" * 60)
print("计算next数组")
print("=" * 60)
pattern = "ABABCABAB"
next_arr = compute_next(pattern)
# 输出:
# ============================================================
# 计算next数组
# ============================================================
# 模式串: ABABCABAB
# 索引: 0 1 2 3 4 5 6 7 8
# 字符: A B A B C A B A B
#
# next[1]: pattern[1](B) != pattern[0](A), j从0回退到next[-1]=0
# → next[1] = 0
#
# next[2]: pattern[2](A) == pattern[0](A), j=1
# → next[2] = 1
#
# next[3]: pattern[3](B) == pattern[1](B), j=2
# → next[3] = 2
#
# next[4]: pattern[4](C) != pattern[2](A), j从2回退到next[1]=0
# → next[4] = 0
#
# next[5]: pattern[5](A) == pattern[0](A), j=1
# → next[5] = 1
#
# next[6]: pattern[6](B) == pattern[1](B), j=2
# → next[6] = 2
#
# next[7]: pattern[7](A) == pattern[2](A), j=3
# → next[7] = 3
#
# next[8]: pattern[8](B) == pattern[3](B), j=4
# → next[8] = 4
#
# next数组: [0, 0, 1, 2, 0, 1, 2, 3, 4]
KMP完整实现¶
def kmp_search(text, pattern):
"""
KMP字符串匹配
时间:O(m+n), m=len(text), n=len(pattern)
空间:O(n) (next数组)
"""
m, n = len(text), len(pattern)
if n == 0:
return []
if m < n:
return []
# 第1步:计算next数组
next_arr = compute_next(pattern)
# 第2步:KMP匹配
print(f"\nKMP匹配过程:")
print(f"文本串: {text}")
print(f"模式串: {pattern}\n")
positions = []
j = 0 # pattern的索引
for i in range(m):
print(f"\n比较T[{i}]({text[i]})和P[{j}]({pattern[j]})", end="")
# 当不匹配时,根据next数组回退j
while j > 0 and text[i] != pattern[j]:
print(f" ✗ 不匹配, j从{j}回退到{next_arr[j-1]}")
j = next_arr[j-1]
print(f" → 比较T[{i}]({text[i]})和P[{j}]({pattern[j]})", end="")
# 匹配,j前进
if text[i] == pattern[j]:
print(" ✓ 匹配")
j += 1
else:
print(" ✗ 不匹配")
# 完全匹配
if j == n:
pos = i - n + 1
positions.append(pos)
print(f" *** 找到匹配,位置{pos} ***")
# 继续查找下一个匹配
j = next_arr[j-1]
print(f" j重置为{j},继续查找")
print(f"\n匹配位置: {positions}")
return positions
# 测试
print("\n" + "=" * 60)
print("KMP算法")
print("=" * 60)
text = "ABABDABACDABABCABAB"
pattern = "ABABCABAB"
positions = kmp_search(text, pattern)
# 输出:
# ============================================================
# KMP算法
# ============================================================
# 模式串: ABABCABAB
# 索引: 0 1 2 3 4 5 6 7 8
# 字符: A B A B C A B A B
# ...
# (next数组计算过程)
# ...
# KMP匹配过程:
# 文本串: ABABDABACDABABCABAB
# 模式串: ABABCABAB
#
# 比较T[0](A)和P[0](A) ✓ 匹配
# 比较T[1](B)和P[1](B) ✓ 匹配
# 比较T[2](A)和P[2](A) ✓ 匹配
# 比较T[3](B)和P[3](B) ✓ 匹配
# 比较T[4](D)和P[4](C) ✗ 不匹配, j从4回退到0
# 比较T[4](D)和P[0](A) ✗ 不匹配
# ...
# (最终找到匹配)
KMP算法图解¶
示例:T="ABABABC", P="ABA"
next数组: [0, 0, 1]
步骤1: i=0, j=0
T: A B A B A B C
P: A . . . . . .
✓ 匹配
步骤2: i=1, j=1
T: A B A B A B C
P: . A . . . . .
✓ 匹配
步骤3: i=2, j=2
T: A B A B A B C
P: . . A . . . .
✓ 匹配
步骤4: i=3, j=3 (j==len(P), 找到匹配!)
位置: i-j = 3-3 = 0
j = next[2] = 1
步骤5: i=3, j=1
T: A B A B A B C
P: . . . A . . .
B vs A ✗
j = next[0] = 0
步骤6: i=3, j=0
T: A B A B A B C
P: . . . . A . .
B vs A ✗
步骤7: i=4, j=0
T: A B A B A B C
P: . . . . A . .
A vs A ✓
...
最终匹配位置: [0, 2]
Rabin-Karp算法¶
算法思想¶
核心思想:使用哈希来快速判断文本串的子串是否等于模式串
滚动哈希¶
计算子串哈希:
T = "ABCD", P = "BC"
第一步:计算"AB"的哈希
hash("AB") = hash(A) * base + hash(B)
第二步:滑动到"BC"
hash("BC") = (hash("AB") - hash(A) * base^(n-1)) * base + hash(C)
= 利用hash("AB),O(1)计算!
base: 基数(通常取大质数,如256)
代码实现¶
def rabin_karp_search(text, pattern, base=256, prime=101):
"""
Rabin-Karp字符串匹配
时间:O(mn)最坏, 平均O(m+n)
空间:O(1)
"""
m, n = len(text), len(pattern)
if n == 0:
return []
if m < n:
return []
print(f"文本串: {text}")
print(f"模式串: {pattern}")
print(f"基数: {base}, 质数: {prime}\n")
# 计算模式串的哈希
pattern_hash = 0
for char in pattern:
pattern_hash = (pattern_hash * base + ord(char)) % prime
print(f"模式串哈希: {pattern_hash}")
# 计算text第一个窗口的哈希
text_hash = 0
for i in range(n):
text_hash = (text_hash * base + ord(text[i])) % prime
# base^(n-1) % prime(用于移除最左边的字符)
h = 1
for i in range(n-1):
h = (h * base) % prime
positions = []
print(f"\n滚动哈希过程:")
# 滑动窗口
for i in range(m - n + 1):
print(f"\n位置{i}: ", end="")
# 哈希值相同,验证
if text_hash == pattern_hash:
print(f"哈希匹配({text_hash}), 验证: ", end="")
# 逐字符验证
match = True
for j in range(n):
print(f"{text[i+j]}", end="")
if text[i+j] != pattern[j]:
print(" ✗ 哈希冲突")
match = False
break
if match:
print(" ✓ 完全匹配")
positions.append(i)
else:
print(f"哈希不匹配 ({text_hash} != {pattern_hash})")
# 计算下一个窗口的哈希
if i < m - n:
# 移除最左边的字符,添加新字符
text_hash = (text_hash - ord(text[i]) * h) % prime
text_hash = (text_hash * base + ord(text[i + n])) % prime
text_hash = (text_hash + prime) % prime # 确保正数
print(f"\n匹配位置: {positions}")
return positions
# 测试
print("\n" + "=" * 60)
print("Rabin-Karp算法")
print("=" * 60)
text = "ABABABC"
pattern = "ABA"
positions = rabin_karp_search(text, pattern)
# 输出:
# ============================================================
# Rabin-Karp算法
# ============================================================
# 文本串: ABABABC
# 模式串: ABA
# 基数: 256, 质数: 101
#
# 模式串哈希: 17
#
# 滚动哈希过程:
#
# 位置0: 哈希匹配(17), 验证: ABA ✓ 完全匹配
#
# 位置1: 哈希不匹配 (16 != 17)
#
# 位置2: 哈希匹配(17), 验证: ABA ✓ 完全匹配
#
# 位置3: 哈希不匹配 (16 != 17)
#
# 位置4: 哈希不匹配 (18 != 17)
#
# 匹配位置: [0, 2]
Boyer-Moore算法¶
算法思想¶
核心思想:从右向左匹配,利用坏字符规则和好后缀规则跳过更多字符
从右向左匹配的优势:
如果最右边的字符就不匹配,可以跳过整个模式串的长度!
示例:
T: "HERE IS A SIMPLE EXAMPLE"
P: "EXAMPLE"
从右向左匹配:
T: ...EXAMPLE
P: EXAMPLE
↑
E != P, 没有E在P中,直接跳过7个字符!
T: ...EXAMPLE
P: EXAMPLE
↑
再比较
坏字符规则¶
定义:当T[i] != P[j]时,T[i]称为坏字符
规则:将P向右移动,使P中最右边的T[i]与T[i]对齐
代码实现(简化版:仅坏字符规则)¶
def boyer_moore_search(text, pattern):
"""
Boyer-Moore字符串匹配(简化版:仅坏字符规则)
时间:O(mn)最坏, 通常O(m/n)
空间:O(字符集大小)
"""
m, n = len(text), len(pattern)
if n == 0:
return []
if m < n:
return []
# 预处理:坏字符表
# bad_char[c] = 字符c在pattern中最右出现的位置
bad_char = {}
print("构建坏字符表:")
for i, char in enumerate(pattern):
bad_char[char] = i
print(f" '{char}': 最右位置={i}")
print(f"\n坏字符表: {bad_char}\n")
positions = []
shift = 0 # pattern相对于text的偏移
print("匹配过程:")
while shift <= m - n:
print(f"\n偏移{shift}: ", end="")
j = n - 1 # 从pattern最右边开始
# 从右向左匹配
while j >= 0 and text[shift + j] == pattern[j]:
print(f"T[{shift+j}]{text[shift+j]}==P[{j}]{pattern[j]} ", end="")
j -= 1
# 完全匹配
if j < 0:
print("\n ✓ 完全匹配")
positions.append(shift)
# 继续查找
if shift + n < m:
bad_char_text = text[shift + n]
if bad_char_text in bad_char:
shift += n - bad_char[bad_char_text]
else:
shift += n
else:
break
else:
# 不匹配,应用坏字符规则
bad_char_text = text[shift + j]
print(f"\n ✗ T[{shift+j}]{bad_char_text} != P[{j}]{pattern[j]}")
if bad_char_text in bad_char:
# 坏字符在pattern中
bad_char_pos = bad_char[bad_char_text]
move = max(1, j - bad_char_pos)
shift += move
print(f" 坏字符'{bad_char_text}'在P中的位置: {bad_char_pos}, 向右移动{move}位")
else:
# 坏字符不在pattern中,跳过n位
shift += n
print(f" 坏字符'{bad_char_text}'不在P中, 向右移动{n}位")
print(f"\n匹配位置: {positions}")
return positions
# 测试
print("\n" + "=" * 60)
print("Boyer-Moore算法(简化版)")
print("=" * 60)
text = "ABAAABABCDEFG"
pattern = "ABCD"
positions = boyer_moore_search(text, pattern)
# 输出:
# ============================================================
# Boyer-Moore算法(简化版)
# ============================================================
# 构建坏字符表:
# 'A': 最右位置=0
# 'B': 最右位置=1
# 'C': 最右位置=2
# 'D': 最右位置=3
#
# 坏字符表: {'A': 0, 'B': 1, 'C': 2, 'D': 3}
#
# 匹配过程:
#
# 偏移0:
# ✗ T[3]A != P[3]D
# 坏字符'A'在P中的位置: 0, 向右移动3位
#
# 偏移3:
# ✗ T[6]A != P[3]D
# 坏字符'A'在P中的位置: 0, 向右移动3位
#
# 偏移6: T[9]D==P[3]D T[8]C==P[2]C T[7]B==P[1]B T[6]A==P[0]A
# ✓ 完全匹配
#
# 匹配位置: [6]
Sunday算法¶
算法思想¶
核心思想:Boyer-Moore的改进,关注text中窗口后面的字符
优势:
如果text窗口后的字符不在pattern中,可以跳过整个pattern+1
示例:
T: "SUBSTRING"
P: "SEARCH"
窗口: "SUBSTR"
^ (S)
后面是'I', 'I'不在P中
直接跳过len(P)+1 = 7位!
代码实现¶
def sunday_search(text, pattern):
"""
Sunday字符串匹配
时间:O(mn)最坏, 通常O(m+n)
空间:O(字符集大小)
"""
m, n = len(text), len(pattern)
if n == 0:
return []
if m < n:
return []
# 预处理:记录pattern中每个字符最右位置
char_pos = {}
print("构建字符位置表:")
for i, char in enumerate(pattern):
char_pos[char] = i
print(f" '{char}': 最右位置={i}")
print(f"\n字符位置表: {char_pos}\n")
positions = []
i = 0 # text中的当前窗口起始位置
print("匹配过程:")
while i <= m - n:
print(f"\n窗口[{i}:{i+n}]: {text[i:i+n]}", end="")
# 检查是否匹配
j = 0
while j < n and text[i+j] == pattern[j]:
j += 1
if j == n:
print(" ✓ 匹配")
positions.append(i)
else:
print(f" ✗ 不匹配(位置{j}: {text[i+j]} != {pattern[j]})")
# 查看窗口后的字符
if i + n < m:
next_char = text[i + n]
print(f" 窗口后字符: '{next_char}'")
if next_char in char_pos:
# 字符在pattern中
shift = n - char_pos[next_char]
print(f" '{next_char}'在P中位置{char_pos[next_char]}, 向右移动{shift}位")
else:
# 字符不在pattern中,跳过n+1位
shift = n + 1
print(f" '{next_char}'不在P中, 向右移动{shift}位")
i += shift
else:
break
print(f"\n匹配位置: {positions}")
return positions
# 测试
print("\n" + "=" * 60)
print("Sunday算法")
print("=" * 60)
text = "SUBSTRINGSEARCH"
pattern = "SEARCH"
positions = sunday_search(text, pattern)
# 输出:
# ============================================================
# Sunday算法
# ============================================================
# 构建字符位置表:
# 'S': 最右位置=0
# 'E': 最右位置=1
# 'A': 最右位置=2
# 'R': 最右位置=3
# 'C': 最右位置=4
# 'H': 最右位置=5
#
# 字符位置表: {'S': 0, 'E': 1, 'A': 2, 'R': 3, 'C': 4, 'H': 5}
#
# 匹配过程:
#
# 窗口[0:6]: SUBSTR ✗ 不匹配(位置1: U != E)
# 窗口后字符: 'I'
# 'I'不在P中, 向右移动7位
#
# 窗口[7:13]: SEARCH ✓ 匹配
# 窗口后字符: 'A'
# 'A'在P中位置2, 向右移动4位
#
# 匹配位置: [7]
字符串处理技巧¶
技巧1:双指针¶
def is_palindrome(s):
"""
判断回文串(双指针)
时间:O(n)
空间:O(1)
"""
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
# 测试
print("回文检测:")
print(f"'racecar': {is_palindrome('racecar')}") # True
print(f"'hello': {is_palindrome('hello')}") # False
技巧2:滑动窗口¶
def length_of_longest_substring(s):
"""
最长无重复子串(LeetCode 3)
滑动窗口
"""
char_index = {}
left = 0
max_len = 0
for right, char in enumerate(s): # enumerate同时获取索引和值
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
char_index[char] = right
max_len = max(max_len, right - left + 1)
return max_len
# 测试
print("\n最长无重复子串:")
print(f"'abcabcbb': {length_of_longest_substring('abcabcbb')}") # 3 ("abc")
print(f"'bbbbb': {length_of_longest_substring('bbbbb')}") # 1 ("b")
print(f"'pwwkew': {length_of_longest_substring('pwwkew')}") # 3 ("wke")
技巧3:前缀和¶
def prefix_sum_hashes(s):
"""
字符串前缀哈希(用于快速计算子串哈希)
"""
n = len(s)
prefix = [0] * (n + 1)
power = [1] * (n + 1)
base = 131
mod = 2**64
for i in range(n):
prefix[i+1] = (prefix[i] * base + ord(s[i])) % mod
power[i+1] = (power[i] * base) % mod
return prefix, power
def get_hash(prefix, power, l, r):
"""获取子串[l, r]的哈希"""
return (prefix[r+1] - prefix[l] * power[r-l+1]) % (2**64)
LeetCode高频真题¶
题目1:最长公共前缀¶
LeetCode 14. 最长公共前缀
def longest_common_prefix(strs):
"""
最长公共前缀(水平扫描)
时间:O(mn), m=字符串数, n=平均长度
"""
if not strs:
return ""
prefix = strs[0]
print(f"初始前缀: {prefix}")
for s in strs[1:]: # 切片操作:[start:end:step]提取子序列
print(f"\n比较: '{prefix}' 和 '{s}'")
while s.find(prefix) != 0:
prefix = prefix[:-1]
print(f" 不匹配,缩短前缀: '{prefix}'")
if not prefix:
return ""
print(f"\n最长公共前缀: '{prefix}'")
return prefix
# 测试
print("\n" + "=" * 60)
print("最长公共前缀")
print("=" * 60)
strs = ["flower", "flow", "flight"]
result = longest_common_prefix(strs)
# 输出:'fl'
题目2:反转字符串¶
LeetCode 344. 反转字符串
def reverse_string(s):
"""
反转字符串(双指针)
时间:O(n)
空间:O(1)
"""
s = list(s)
left, right = 0, len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return ''.join(s)
# 测试
print("\n反转字符串:")
print(f"'hello' → '{reverse_string('hello')}'") # 'olleh'
题目3:字符串相乘¶
LeetCode 43. 字符串相乘
def multiply(num1, num2):
"""
字符串相乘(模拟手工乘法)
时间:O(mn)
"""
if num1 == "0" or num2 == "0":
return "0"
m, n = len(num1), len(num2)
result = [0] * (m + n)
print(f" {num1}")
print(f"× {num2}")
print(f"{'-' * (max(m, n) + 2)}")
# 从右向左相乘
for i in range(m-1, -1, -1):
for j in range(n-1, -1, -1):
mul = (ord(num1[i]) - ord('0')) * (ord(num2[j]) - ord('0'))
p1, p2 = i + j, i + j + 1
# 加到结果中
total = mul + result[p2]
result[p2] = total % 10
result[p1] += total // 10
# 转换为字符串
result_str = ''.join(map(str, result)).lstrip('0')
return result_str
# 测试
print("\n字符串相乘:")
print(f"123 × 456 = {multiply('123', '456')}") # "56088"
算法对比总结¶
字符串匹配算法对比¶
| 算法 | 预处理时间 | 匹配时间 | 最坏情况 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|---|
| 暴力 | O(1) | O(mn) | O(mn) | 简单 | 效率低 | 短串 |
| KMP | O(m) | O(n) | O(m+n) | 保证线性 | 复杂 | 通用 |
| Rabin-Karp | O(m) | O(mn)平均O(m+n) | O(mn) | 多模式匹配 | 哈希冲突 | 多模式 |
| Boyer-Moore | O(m+σ) | O(mn)通常O(m/n) | O(mn) | 实际最快 | 复杂 | 英文文本 |
| Sunday | O(m) | O(mn)通常O(m+n) | O(mn) | 简单高效 | 较新 | 实际应用 |
选择建议¶
短串匹配(n < 5):
→ 暴力算法 ✓
一般用途:
→ KMP算法 ✓(保证线性)
多模式匹配:
→ Rabin-Karp ✓
英文文本搜索:
→ Boyer-Moore ✓(实际最快)
实际工程:
→ Sunday ✓(简单高效)
📝 总结¶
字符串算法核心要点¶
✅ 暴力匹配: - 简单直观 - O(mn)时间 - 短串可用
✅ KMP算法:⭐⭐⭐⭐⭐ - next数组(前缀表) - 利用已匹配信息 - 保证O(m+n)时间 - 面试必考!
✅ Rabin-Karp: - 滚动哈希 - 适合多模式匹配 - 注意哈希冲突
✅ Boyer-Moore: - 从右向左匹配 - 坏字符规则 - 实际应用最快
✅ Sunday算法: - BM改进 - 关注窗口后字符 - 简单高效
大厂面试重点¶
必考: - KMP算法(手写next数组) - 字符串匹配(暴力→KMP优化) - 回文串(双指针) - 最长公共前缀
高频: - 滑动窗口 - 字符串反转 - 子串问题
下一步¶
继续学习: - 回溯算法 - 全排列、N皇后 - LeetCode 100题 - 高频真题
继续学习回溯算法...