字符串算法详解

学习建议

字符串算法在文本处理、搜索引擎、生物信息学等领域应用广泛。预计学习时间 2-3 周。

一、字符串算法的重要性 🔤

字符串是计算机中最基本的数据类型之一,从简单的文本编辑到复杂的搜索引擎,都离不开高效的字符串算法。

应用领域

二、基础字符串操作 📝

1. 字符串匹配(暴力法)

最简单的方法:逐个位置尝试匹配。

暴力字符串匹配
def brute_force_search(text, pattern):
    """
    暴力字符串匹配
    :param text: 文本
    :param pattern: 模式串
    :return: 所有匹配位置的列表
    """
    n = len(text)
    m = len(pattern)
    positions = []
    
    # 遍历文本的每个可能起始位置
    for i in range(n - m + 1):
        # 检查从位置 i 开始是否匹配
        match = True
        for j in range(m):
            if text[i + j] != pattern[j]:
                match = False
                break
        
        if match:
            positions.append(i)
    
    return positions

# 测试
text = "AABAACAADAABAABA"
pattern = "AABA"
print(brute_force_search(text, pattern))  # [0, 9, 12]

时间复杂度:O(n × m),n 为文本长度,m 为模式长度

性能问题

暴力法在最坏情况下效率很低,特别是当文本和模式有很多部分匹配时。

2. KMP 算法

KMP(Knuth-Morris-Pratt)算法通过预处理模式串,避免不必要的回溯,将时间复杂度降到 O(n + m)。

KMP 算法实现
def kmp_search(text, pattern):
    """
    KMP 字符串匹配算法
    :param text: 文本
    :param pattern: 模式串
    :return: 所有匹配位置的列表
    """
    n = len(text)
    m = len(pattern)
    
    if m == 0 or n < m:
        return []
    
    # 构建 next 数组(部分匹配表)
    next_array = build_next_array(pattern)
    
    positions = []
    i = j = 0
    
    while i < n:
        if text[i] == pattern[j]:
            i += 1
            j += 1
            
            # 找到完整匹配
            if j == m:
                positions.append(i - j)
                j = next_array[j - 1]
        else:
            if j > 0:
                j = next_array[j - 1]
            else:
                i += 1
    
    return positions

def build_next_array(pattern):
    """构建 KMP 的 next 数组"""
    m = len(pattern)
    next_array = [0] * m
    length = 0  # 前一个最长前后缀的长度
    i = 1
    
    while i < m:
        if pattern[i] == pattern[length]:
            length += 1
            next_array[i] = length
            i += 1
        else:
            if length > 0:
                length = next_array[length - 1]
            else:
                next_array[i] = 0
                i += 1
    
    return next_array

# 测试
text = "ABABDABACDABABCABAB"
pattern = "ABABCABAB"
print(kmp_search(text, pattern))  # [10]
code
模式串: A B A B C A B A B
next[]: 0 0 1 2 0 1 2 3 4

核心思想:利用已匹配的信息,避免重复比较

next 数组示例

3. Rabin-Karp 算法

Rabin-Karp 使用哈希函数进行快速匹配,特别适合多模式匹配。

python
def rabin_karp_search(text, pattern, prime=101):
    """
    Rabin-Karp 字符串匹配
    :param text: 文本
    :param pattern: 模式串
    :param prime: 质数(用于哈希)
    :return: 所有匹配位置的列表
    """
    n = len(text)
    m = len(pattern)
    d = 256  # 字符集大小
    
    if m == 0 or n < m:
        return []
    
    # 计算 d^(m-1) % prime
    h = pow(d, m - 1, prime)
    
    # 计算模式和文本第一个窗口的哈希值
    pattern_hash = 0
    text_hash = 0
    
    for i in range(m):
        pattern_hash = (d * pattern_hash + ord(pattern[i])) % prime
        text_hash = (d * text_hash + ord(text[i])) % prime
    
    positions = []
    
    # 滑动窗口
    for i in range(n - m + 1):
        # 哈希值匹配,进一步验证(避免哈希冲突)
        if pattern_hash == text_hash:
            if text[i:i+m] == pattern:
                positions.append(i)
        
        # 计算下一个窗口的哈希值
        if i < n - m:
            text_hash = (d * (text_hash - ord(text[i]) * h) + 
                        ord(text[i + m])) % prime
            # 处理负数
            if text_hash < 0:
                text_hash += prime
    
    return positions

# 测试
text = "AABAACAADAABAABA"
pattern = "AABA"
print(rabin_karp_search(text, pattern))  # [0, 9, 12]

优势

劣势

三、高级字符串算法 🚀

1. Trie 树(前缀树)

Trie 树是一种树形数据结构,用于高效存储和检索字符串集合。

Trie 树实现
class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        """插入单词"""
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end_of_word = True
    
    def search(self, word):
        """搜索单词是否存在"""
        node = self._find_node(word)
        return node is not None and node.is_end_of_word
    
    def starts_with(self, prefix):
        """判断是否有以 prefix 为前缀的单词"""
        return self._find_node(prefix) is not None
    
    def _find_node(self, prefix):
        """查找前缀对应的节点"""
        node = self.root
        for char in prefix:
            if char not in node.children:
                return None
            node = node.children[char]
        return node

# 测试
trie = Trie()
trie.insert("apple")
trie.insert("app")
trie.insert("application")

print(trie.search("apple"))      # True
print(trie.search("app"))        # True
print(trie.search("appl"))       # False
print(trie.starts_with("app"))   # True

应用场景

时间复杂度

2. 后缀数组

后缀数组用于快速解决字符串的子串查询问题。

后缀数组简化版
def build_suffix_array(text):
    """
    构建后缀数组
    :param text: 输入字符串
    :return: 后缀数组(排序后的起始位置)
    """
    n = len(text)
    # 生成所有后缀及其起始位置
    suffixes = [(text[i:], i) for i in range(n)]
    # 按字典序排序
    suffixes.sort()
    # 返回起始位置数组
    return [suffix[1] for suffix in suffixes]

def longest_common_prefix(text, suffix_array):
    """计算相邻后缀的最长公共前缀"""
    n = len(text)
    sa = suffix_array
    lcp = [0] * n
    
    for i in range(1, n):
        # 计算 sa[i-1] 和 sa[i] 对应后缀的 LCP
        k = 0
        while (sa[i-1] + k < n and sa[i] + k < n and 
               text[sa[i-1] + k] == text[sa[i] + k]):
            k += 1
        lcp[i] = k
    
    return lcp

# 测试
text = "banana"
sa = build_suffix_array(text)
print("后缀数组:", sa)
# [5, 3, 1, 0, 4, 2]
# 对应后缀: a, ana, anana, banana, na, nana

应用

3. Manacher 算法

Manacher 算法用于在 O(n) 时间内找到最长回文子串。

python
def manacher(s):
    """
    Manacher 算法求最长回文子串
    :param s: 输入字符串
    :return: 最长回文子串
    """
    # 预处理:插入特殊字符,统一奇偶长度
    t = '#' + '#'.join(s) + '#'
    n = len(t)
    
    # p[i] 表示以 t[i] 为中心的最长回文半径
    p = [0] * n
    center = right = 0
    max_len = max_center = 0
    
    for i in range(n):
        # 利用对称性
        if i < right:
            mirror = 2 * center - i
            p[i] = min(right - i, p[mirror])
        
        # 尝试扩展
        while (i + p[i] + 1 < n and i - p[i] - 1 >= 0 and 
               t[i + p[i] + 1] == t[i - p[i] - 1]):
            p[i] += 1
        
        # 更新中心和右边界
        if i + p[i] > right:
            center = i
            right = i + p[i]
        
        # 更新最长回文
        if p[i] > max_len:
            max_len = p[i]
            max_center = i
    
    # 提取最长回文子串
    start = (max_center - max_len) // 2
    return s[start:start + max_len]

# 测试
print(manacher("babad"))    # "bab" 或 "aba"
print(manacher("cbbd"))     # "bb"
print(manacher("racecar"))  # "racecar"

时间复杂度:O(n),线性时间

四、字符串算法对比 📊

算法 时间复杂度 空间复杂度 适用场景
暴力匹配 O(n×m) O(1) 短模式串
KMP O(n+m) O(m) 单模式匹配
Rabin-Karp O(n+m) 平均 O(1) 多模式匹配
Trie O(m) 搜索 O(n×m) 前缀查询、自动补全
后缀数组 O(n log n) 构建 O(n) 子串查询
Manacher O(n) O(n) 最长回文子串

选择指南

五、实际应用场景 💼

场景一:搜索引擎

搜索引擎使用多种字符串算法:

场景二:DNA 序列分析

生物信息学中大量使用字符串算法:

python
def find_dna_motif(dna_sequence, motif):
    """在 DNA 序列中查找基序"""
    return kmp_search(dna_sequence, motif)

# 测试
dna = "ATCGATCGATCGATCG"
motif = "ATCG"
positions = find_dna_motif(dna, motif)
print(f"基序 '{motif}' 出现在位置: {positions}")

场景三:代码编辑器

现代代码编辑器使用字符串算法实现:

场景四: plagiarism 检测

检测文本相似度:

Rabin-Karp 的应用
将文档分成多个片段,计算每个片段的哈希值。 通过比较哈希值的重合度,判断是否存在抄袭。 这种方法比逐字比较快得多!

六、字符串算法的优化技巧 ⚡

技巧一:预处理加速

许多字符串算法通过预处理提升效率:

  • KMP:构建 next 数组
  • Rabin-Karp:预计算哈希
  • Trie:批量插入构建

原则:如果多次查询,值得花时间预处理。

技巧二:空间换时间

Trie 和后缀数组都使用了额外空间来加速查询。

权衡

  • 内存充足 → 用空间换时间
  • 内存受限 → 考虑在线算法
技巧三:并行化处理

对于大规模文本,可以并行处理:

  • 分块后并行匹配
  • GPU 加速哈希计算
  • 分布式 Trie 构建
陷阱:编码问题

处理多语言文本时注意:

  • UTF-8 编码的字符可能占多个字节
  • 某些语言的字符有组合形式
  • 大小写转换要考虑语言特性

建议:使用成熟的库(如 Python 的 re 模块)

七、练习与挑战 🏆

推荐练习题

LeetCode 28. 实现 strStr()

实现 strStr() 函数,返回 needle 在 haystack 中第一次出现的位置。 难度:⭐ 简单 可使用 KMP 或暴力法

LeetCode 208. 实现 Trie

实现一个 Trie(前缀树),包含 insert、search 和 startsWith 操作。 难度:⭐⭐ 中等 经典 Trie 实现

LeetCode 5. 最长回文子串

给定一个字符串,找到其中最长的回文子串。 难度:⭐⭐ 中等 可使用 Manacher 或 DP

LeetCode 76. 最小覆盖子串

给你一个字符串 s 和一个字符串 t,返回 s 中涵盖 t 所有字符的最小子串。 难度:⭐⭐ 困难 滑动窗口 + 字符串处理

八、学习路径建议 🗺️

第1-5天
基础匹配
掌握暴力法、KMP、Rabin-Karp 等匹配算法。
第6-10天
数据结构
学习 Trie 树、后缀数组等字符串数据结构。
第11-15天
高级算法
深入研究 Manacher、AC 自动机等高级算法。
第16-21天
实战练习
完成至少 20 道 LeetCode 字符串题目。

九、总结 🎯

字符串算法是计算机科学的基础,掌握它们能大幅提升你处理文本数据的能力。

探索字符串世界

字符串算法的世界非常精彩,还有 AC 自动机、Burrows-Wheeler 变换等更多内容等待你探索!

查看字符串专题练习 下载完整代码

© 2026 AI未来实验室 · 用 AI 探索无限可能
站点地图 | Robots.txt | 关于