Typescript-Algorithms
    Preparing search index...

    Knuth-Morris-Pratt.png

    KMP 原理讲解

    定义 s 的真前缀为不等于 s 的前缀,s 的真后缀为不等于 s 的后缀。

    定义 s 的 border 为既是 s 的真前缀又是 s 的真后缀的字符串。例如在 s=aabcaa 中,a 和 aa 都是 s 的 border。

    对于模式串 p 的每个前缀 p[:i],计算这个前缀的最长 border 长度,记在 π 数组中。

    利用 π 数组,可以快速计算模式串 p 出现在文本串 t 的哪些位置上。

    注:π 数组的定义参考《算法导论》,国内数据结构教材通常定义为 next 数组。以严蔚敏那本为例,二者的关系是 next[i+1]=π[i]+1,即 π 数组整体右移一位,元素值加一。

    模板:

    Python3

    Java

    C++

    Go

    # 在文本串 text 中查找模式串 pattern,返回所有成功匹配的位置(pattern[0] 在 text 中的下标)
    def kmp(text: str, pattern: str) -> List[int]:
        m = len(pattern)
        pi = [0] * m
        cnt = 0
        for i in range(1, m):
            b = pattern[i]
            while cnt and pattern[cnt] != b:
                cnt = pi[cnt - 1]
            if pattern[cnt] == b:
                cnt += 1
            pi[i] = cnt
    
        pos = []
        cnt = 0
        for i, b in enumerate(text):
            while cnt and pattern[cnt] != b:
                cnt = pi[cnt - 1]
            if pattern[cnt] == b:
                cnt += 1
            if cnt == len(pattern):
                pos.append(i - m + 1)
                cnt = pi[cnt - 1]
        return pos
    

    :在国内算法竞赛圈,这个算法也叫扩展 KMP。

    对于字符串 s,定义 z[i] 表示后缀 s[i:] 与 s 的 LCP(最长公共前缀)的长度,其中 s[i:] 表示从 s[i] 到 s[n−1] 的子串。

    常用技巧是构造字符串 pattern+s,如果发现 z[m+i]≥m(m 是 pattern 的长度),则说明从 s[i] 开始的子串与 pattern 匹配。

    所以上面的一些 KMP 题目(子串匹配相关的),也可以用 Z 函数解决。读者可以尝试用 Z 函数解决 28. 找出字符串中第一个匹配项的下标

    模板:

    Python3

    Java

    C++

    Go

    # 计算并返回 z 数组,其中 z[i] = |LCP(s[i:], s)|
    def calc_z(s: str) -> List[int]:
        n = len(s)
        z = [0] * n
        box_l = box_r = 0
        for i in range(1, n):
            if i <= box_r:
                z[i] = min(z[i - box_l], box_r - i + 1)
            while i + z[i] < n and s[z[i]] == s[i + z[i]]:
                box_l, box_r = i, i + z[i]
                z[i] += 1
        z[0] = n
        return z
    

    LCP 数组

    Manacher 算法可以计算以 s[i](或者 s[i] 和 s[i+1])为回文中心的最长回文子串的长度。

    此外,还可以:

    • 判断任意子串是否为回文串。
    • 计算从 s[i] 开始的最长回文子串的长度。
    • 计算以 s[i] 结尾的最长回文子串的长度。

    Z 函数和 Manacher 算法都会用到类似 Z-box 的概念,在学习时,可以对比体会。

    本题单的大多数题目都可以用字符串哈希解决。

    推荐先把 2156. 查找给定哈希值的子串 做了,对理解多项式哈希的计算方法有帮助。

    模板代码我的题解,包含单模哈希和双模哈希。

    定义循环左移操作:把字符串 s 的第一个字符 s[0] 移除,添加到 s 的末尾。例如 abcd 操作一次后得到 bcda。

    问题:你可以执行任意次循环左移操作,计算你能得到的字典序最小的字符串。

    推荐先完成 1163. 按字典序排在最后的子串

    AC 自动机 = 字典树 + KMP。

    由于这些题目也可以用其他算法(字符串哈希等)解决,难度分仅供参考。

    由于这些题目也可以用其他算法(字符串哈希等)解决,难度分仅供参考。

    上面都是和子串相关的算法,本节是和子序列相关的算法:子序列自动机。

    虽然名字有些高大上,但实际上只是预处理 ≥i 的最近字母 c 的下标而已。

    讲解 中的「进阶问题」。

    如何科学刷题?

    1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
    2. 二分查找(二分答案/最小化最大值/最大化最小值/第K小)
    3. 单调栈(基础/矩形面积/贡献法/最小字典序)
    4. 网格图(DFS/BFS/综合应用)
    5. 位运算(基础/性质/拆位/试填/恒等式/思维)
    6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
    7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
    8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
    9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
    10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
    11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
    12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

    我的题解精选(已分类)

    欢迎关注 B站@灵茶山艾府

    如果你发现有题目可以补充进来,欢迎评论反馈。