跳转至

字符串算法(String Algorithms)完全详解 - 从暴力到KMP

重要性:⭐⭐⭐⭐⭐ 难度:⭐⭐⭐⭐ 学习时间:7-10天 大厂面试频率:极高(字节、腾讯、阿里必考) 前置知识:数组、循环、基础算法


📚 目录

  1. 字符串基础
  2. 暴力匹配算法
  3. KMP算法⭐⭐⭐⭐⭐
  4. Rabin-Karp算法
  5. Boyer-Moore算法
  6. Sunday算法
  7. 字符串处理技巧
  8. LeetCode高频真题
  9. 算法对比总结

字符串基础

为什么字符串算法重要?

应用极其广泛: - 搜索引擎(Google、百度) - 文本编辑器(查找、替换) - 生物信息(DNA序列匹配) - 数据压缩 - 编译器(词法分析) - 网络安全(入侵检测)

面试必考: - 字符串匹配(KMP必问!) - 字符串操作(反转、分割) - 正则表达式 - 字符串DP(编辑距离)

字符串的内存表示

Python
# 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的所有出现位置

Text Only
示例:
文本串 T = "ABABDABACDABABCABAB"
模式串 P = "ABABCABAB"

查找:P在T中出现的位置?

位置7: "ABABCABAB"
       匹配!

T: ABABDABACDABABCABAB
     P第一次出现

暴力匹配算法

算法思想

最朴素的方法:从T的每个位置开始,尝试匹配P

Text Only
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匹配

代码实现

Python
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]

时间复杂度分析

Text Only
最坏情况:
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数组),智能跳过不可能匹配的位置

图解对比

Text Only
暴力算法:
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]的最长相同真前缀和真后缀的长度

Text Only
示例: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数组

Python
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完整实现

Python
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算法图解

Text Only
示例: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算法

算法思想

核心思想:使用哈希来快速判断文本串的子串是否等于模式串

Text Only
如果两个串的哈希值不同,一定不同
如果两个串的哈希值相同,可能相同(需要验证)

优势:可以O(1)时间计算滑动窗口的哈希

滚动哈希

Text Only
计算子串哈希:
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)

代码实现

Python
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算法

算法思想

核心思想:从右向左匹配,利用坏字符规则好后缀规则跳过更多字符

Text Only
从右向左匹配的优势:
如果最右边的字符就不匹配,可以跳过整个模式串的长度!

示例:
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]对齐

Text Only
P: "EXAMPLE"
坏字符: 'S'

'S'不在P中 → 跳过len(P) = 7个字符
'S'在P中 → 跳过使'S'对齐

代码实现(简化版:仅坏字符规则)

Python
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 Only
优势:
如果text窗口后的字符不在pattern中,可以跳过整个pattern+1

示例:
T: "SUBSTRING"
P: "SEARCH"

窗口: "SUBSTR"
       ^      (S)
      后面是'I', 'I'不在P中
      直接跳过len(P)+1 = 7位!

代码实现

Python
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:双指针

Python
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:滑动窗口

Python
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:前缀和

Python
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. 最长公共前缀

Python
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. 反转字符串

Python
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. 字符串相乘

Python
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) 简单高效 较新 实际应用

选择建议

Text Only
短串匹配(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题 - 高频真题


继续学习回溯算法...