跳转至

第4章 串

2027 王道 · 专题阅读 · 保留代码与校订注 · 与版本锁一致

第4章 串

⚠️ 大纲仅要求掌握字符串模式匹配,重点是理解 KMP 算法的原理、next 数组和 nextval 数组的推导过程。 💡 教材建议:手算 next 数组时,可先求部分匹配值表再转换,也可直接用递推公式。

回源 · PDF 第 121 页 · b_121_5

【知识树】第4章骨架

  • 串的定义:子串、主串、位置、空串 vs 空格串
  • 串的基本操作与最小操作子集
  • 串的存储:定长顺序、堆分配、块链
  • 模式匹配
  • 简单(暴力)匹配:O(nm)
  • KMP:部分匹配值 PM → next 数组 → 匹配过程
  • 优化:nextval 数组
回源 · PDF 第 121 页 · b_121_0;PDF 第 121 页 · b_121_2

4.1 串的定义和实现

空串和空格串是一回事吗?

⚠️ 不是。 - 空串:长度 n = 0,用 ∅ 表示 - 空格串:由一个或多个空格组成的串,⚠️ 空格是一种特殊字符,其长度等于空格字符的个数

其他术语:子串(串中任意多个连续字符组成的子序列)、主串、位置(⚠️ 子串在主串中的位置以其第 1 个字符的位置标识)。 串相等的条件:⚠️ 长度相等且对应位置上的字符完全相同。

💡 教材例:A='China Beijing'、B='Beijing'、C='China',长度分别 13、7、5;B 在 A 中的位置为 7,C 在 A 中的位置为 1。

回源 · PDF 第 121 页 · b_121_7;PDF 第 121 页 · b_121_9

串和线性表的区别在哪?

  • 逻辑结构极为相似,区别仅在于串的数据对象被限定为字符集
  • ⚠️ 基本操作差异显著:线性表以单个元素为单位(查找/插入/删除某个元素);串一般以子串为单位(查找/插入/删除一个子串)
回源 · PDF 第 122 页 · b_122_3

串类型的最小操作子集是哪五个?

StrAssign(赋值)、StrCompare(比较)、StrLength(求长)、Concat(联接)、SubString(求子串)

⚠️ 它们无法通过其他串操作实现;⚠️ 其余串操作(除 ClearString 和 DestroyString 外)均可基于该最小子集实现。

💡 记忆法:"赋、比、长、接、取"。

回源 · PDF 第 122 页 · b_122_3;PDF 第 122 页 · b_122_4

三种存储结构各是什么?

  • 定长顺序存储:char ch[MAXLEN]; int length; —— ⚠️ 超出 MAXLEN 的部分被舍弃,称为截断
  • 串长两种表示:用整型变量显式记录;或末尾加不计入串长的结束标记 '\0'(⚠️ 此时串长是隐含值,要遍历计算)
  • 堆分配存储:char *ch; int length; —— ⚠️ 仍是地址连续的存储单元,但空间在运行时用 malloc() 动态申请,free() 释放
  • 块链存储:⚠️ 每个链表结点可存放一个或多个字符,每个结点称为块;最后一块占不满时通常用 "#" 补齐
回源 · PDF 第 122 页 · b_122_7

4.2 串的模式匹配

暴力匹配的复杂度怎么算出来的?

主串长 n、模式串长 m:⚠️ 最多需进行 n − m + 1 趟匹配,每趟最多比较 m 次 → 最坏 O(nm)。

失配时的回溯:i = i − j + 2; j = 1;(主串指针后退,模式串从头开始)。

💡 教材的极端例子:模式串 '0000001'、主串是大量 0 —— 每趟都要到模式串最后一个字符才发现不匹配,主串指针 i 回溯 39 次,总比较 40×7 = 280 次。

回源 · PDF 第 123 页 · b_123_10;PDF 第 124 页 · b_124_4

什么是前缀、后缀、部分匹配值?

  • 前缀:⚠️ 除最后一个字符外,字符串的所有头部子串
  • 后缀:⚠️ 除第一个字符外,字符串的所有尾部子串
  • 部分匹配值:最长相等前后缀的长度

教材例 'ababa' 逐位推导: - 'a' → 前后缀均为空 → 0 - 'ab' → {a} ∩ {b} = ∅ → 0 - 'aba' → {a,ab} ∩ {a,ba} = {a} → 1 - 'abab' → {a,ab,aba} ∩ {b,ab,bab} = {ab} → 2 - 'ababa' → 交集 {a,aba} → 取最长 → 3

所以 'ababa' 的 PM 值为 00123。

回源 · PDF 第 125 页 · b_125_0

PM 表怎么指导右滑?(右滑公式)

\[\text{右滑位数} = \text{已匹配字符数} - \text{对应的部分匹配值}\]

教材例:主串 'ababcabcacbab',模式串 'abcac'(PM = 00010) - 第一趟:第 3 位失配,已匹配 2 个 'ab',最后匹配字符 b 的 PM = 0 → 2 − 0 = 2,右滑 2 位 - 第二趟:第 5 位失配,已匹配 4 个 'abca',最后匹配字符 a 的 PM = 1 → 4 − 1 = 3,右滑 3 位 - 第三趟:全部匹配成功

⚠️ 两种情形都用同一个公式:PM = 0 时滑动最大(首字符直接对齐当前失配位置);PM > 0 时滑到与相等后缀对齐的位置。 ⚠️ 特殊情况:模式串第一个字符就失配 → 已匹配字符数为 0 → 整体右移一位。

💡 主串指针始终不回溯,这使 KMP 的时间复杂度稳定为 O(n + m)。

回源 · PDF 第 125 页 · b_125_12;PDF 第 125 页 · b_125_21

next 数组和 PM 表是什么关系?(手算捷径)

next[j] 的含义:⚠️ 当模式串第 j 个字符失配时,下一轮应从模式串的第 next[j] 个位置继续与主串当前字符比较。

推导:

\[\text{next}[j] = j - [(j-1) - \text{PM}[j-1]] = \text{PM}[j-1] + 1\]

⚠️ 结论:next 数组 = PM 表右移一位,再整体加 1。 - ⚠️ 右移后空出的第一个位置补 0 —— 因为首字符失配时规定 next[1] = 0,表示主串和模式串同步右移一位 - ⚠️ PM 表最后一个元素右移后移出舍去 —— 因为它本是供下一个字符使用的,而最后一个字符没有下一个

例 'abcac':PM = 0 0 0 1 0 → next = 0 1 1 1 2。 ⚠️ 先声明约定,再算数组: - 王道 1 基约定:next[1]=0,next[j]=PM[j-1]+1;失配时 j=next[j],j==0 时才同时后移 i,j - 0 基、-1 哨兵约定:next[0]=-1,j>0 时 next[j]=PM[j-1];失配时同样 j=next[j],j==-1 时同时后移 i,j

💡 例 aabaa:PM = 0 1 0 1 2;王道 1 基 next = 0 1 2 1 2;-1 哨兵 next = -1 0 1 0 1。 ⚠️ 校订/边界:两套值相差 1,但只有在首项哨兵、下标范围和失配分支一起替换时才可转换;不能仅凭“选项从 0 开始”就机械加减。 💡 王道固定规则:next[1] = 0,next[2] = 1。

回源 · PDF 第 127 页 · b_127_0

next 数组的一般公式?

\[\text{next}[j] = \begin{cases} 0, & j = 1 \\ \max\{k \mid 1 < k < j,\ 'p_1\cdots p_{k-1}' = 'p_{j-k+1}\cdots p_{j-1}'\}, & \text{集合非空} \\ 1, & \text{其他情况} \end{cases}\]

💡 直观含义:找模式串已匹配部分中"最长的相等前后缀",让前缀滑过去顶替后缀的位置。 ⚠️ 不存在这样的相等前后缀时(视为 k = 1),主串第 i 个字符与模式串第 1 个字符重新比较。

回源 · PDF 第 127 页 · b_127_19

next 数组的递推怎么做?(两种情况)

设 next[j] = k,求 next[j+1]: - 若 pₖ = pⱼ → ⚠️ next[j+1] = next[j] + 1 = k + 1 - 若 pₖ ≠ pⱼ → ⚠️ 把求 next 视为一个模式匹配问题:令 k = next[k] 继续比较,若仍不匹配则继续 next[next[k]]…,直到找到某个 k′ 满足条件 → next[j+1] = k′ + 1;⚠️ 若始终找不到,则 next[j+1] = 1

教材算例(模式串 a b a a b c a b a,已知 next[1..6] = 0 1 1 2 2 3): - 求 next[7]:next[6]=3,p₆ ≠ p₃ → 比较 p₆ 与 p₁(next[3]=1),p₆ ≠ p₁,next[1]=0 → next[7] = 1 - 求 next[8]:p₇ = p₁ → next[8] = next[7] + 1 = 2 - 求 next[9]:p₈ = p₂ → next[9] = 3

回源 · PDF 第 128 页 · b_128_16

KMP 匹配过程与暴力匹配差在哪一行? ★1考

int Index_KMP(SString S, SString T, int next[]) {
    int i = 1, j = 1;
    while (i <= S.length && j <= T.length) {
        if (j == 0 || S.ch[i] == T.ch[j]) { ++i; ++j; }
        else j = next[j];          // ← 唯一的区别:主串指针 i 不动
    }
    if (j > T.length) return i - T.length;
    else return 0;
}

⚠️ 失配时主串指针 i 保持不变,仅把模式串指针 j 回退到 next[j];⚠️ 当 j = 0 时把 i 和 j 同时加 1(相当于模式串右滑一位,从主串第 i+1 位开始)。

💡 对齐图的理解法:失配前已匹配的后缀与模式串最长相等前缀已知相同,直接把该前缀对齐到后缀下方;i 留在失配字符,j 跳到新的待比位置。若已回退到哨兵,才让 i 前进。

⚠️ 一个反直觉的实务结论:尽管暴力匹配最坏 O(mn)、KMP 为 O(m+n),但一般情况下暴力匹配的平均执行时间往往接近 O(m+n),因此至今仍被采用;KMP 仅在存在大量"部分匹配"时才显著占优,其核心优点在于主串指针不回溯。

★ 考过 2015、2019

回源 · PDF 第 126 页 · b_126_3

nextval 数组解决了 next 的什么缺陷? ★1考

问题:模式串 'aaaab'(next = 0 1 2 3 4)匹配主串 'aaabaaaab'。i=4、j=4 失配时,⚠️ next[4]=3 且 p₃ = p₄ = a,next[3]=2 且 p₂ = p₃,next[2]=1 且 p₁ = p₂ —— 这些回退位置上的字符都与 p₄ 相同,继续比较只会重复同样的失配。

⚠️ 根源:不应出现 pⱼ = p_{next[j]} —— 既然 pⱼ ≠ sᵢ,用与 pⱼ 相同的字符去匹配 sᵢ 必定仍失配。

修正办法:不断把 next[j] 替换为 next[next[j]],直到 pⱼ ≠ p_k 或 k = 0,得到 nextval 数组。 教材例:'aaaab' 的 next = 0 1 2 3 4,nextval = 0 0 0 0 4。

代码关键两行:

if (T.ch[i] != T.ch[j]) nextval[i] = j;
else nextval[i] = nextval[j];      // ← 相同就继承

⚠️ 匹配算法本身不变,只是把 next 换成 nextval。

💡 手算第二法:“有效前后缀”(先声明采用 0 基、-1 哨兵): - 先按 next 找出失配位置前的最长相等前后缀 - 若该前缀之后的字符仍与当前失配字符相同,跳去仍会立即失配,因此继续改用更短的相等前后缀 - 直到“后继字符不同”或回到 -1,这个候选才是 nextval

例 aabaaba:next = -1 0 1 0 1 2 3,筛掉“跳过去还会撞上同一字符”的候选后,nextval = -1 -1 1 -1 -1 1 -1。 ⚠️ 校订/边界:这是 nextval 递推的手算视角,不是另一种匹配算法;换回王道 1 基约定时,哨兵和数值也要整体换算。

★ 考过 2024

回源 · PDF 第 129 页 · b_129_3

【原书试题精选与答案方法】第4章

模式匹配的五个基础判断,怎样一次分清?

对应原书单选 01~05,答案依次为 C、B、C/D、D、B。

  • “求模式串在主串中首次出现的位置”叫模式匹配,不是“求子串”。
  • KMP 的标志不是“模式串不回退”,而是 ⚠️ 主串指针 \(i\) 不回溯;失配时保留 \(i\),令 \(j=next[j]\)。
  • 简单匹配的理论最坏复杂度是 \(O(mn)\);KMP 是 \(O(m+n)\)。原书同时提醒:暴力法在一般数据上的实际表现可能接近 \(O(m+n)\),但考试问复杂度时仍按最坏口径答 \(O(mn)\)。

💡 做判断题时抓住边界:若 \(j\) 回退到仍可比较的模式位置,主串指针 \(i\) 不动,当前字符继续参与比较;若已退到王道 1 基的 \(j=0\)(或 0 基的 \(j=-1\) 哨兵),下一步才让 \(i,j\) 同时前进。

回源 · PDF 第 131 页 · b_131_6;PDF 第 131 页 · b_131_7;PDF 第 131 页 · b_131_8;PDF 第 131 页 · b_131_9;PDF 第 131 页 · b_131_10;PDF 第 131 页 · b_131_11;PDF 第 131 页 · b_131_12;PDF 第 131 页 · b_131_13

原书单选 06 的 next 数组,怎样用 PM 表稳妥手算?

对模式串 ababaaababaa:

  1. 逐个前缀求“最长相等真前缀与真后缀长度”,得到 PM 表;
  2. 把 PM 表整体右移一位,低位补 -1,得到 0 下标口径的 next;
  3. 本题选项采用 1 下标且 \(next[1]=0\) 的口径,因此再整体加 1。

最终选 C:

\[next=(0,1,1,2,3,4,2,2,3,4,5,6).\]

⚠️ next 表最常见的失分点不是前后缀,而是题目到底从 0 还是从 1 编号。算表前先写清口径。

回源 · PDF 第 131 页 · b_131_14;PDF 第 131 页 · b_131_15;PDF 第 131 页 · b_131_16;PDF 第 131 页 · b_131_17;PDF 第 131 页 · b_131_18;PDF 第 131 页 · b_131_19

原书单选 07~09,比较次数和 nextval 滑动距离怎么算?

答案:07 选 B(9 次);08 选 C(7 次);09 选 B(右滑 2 位)。

  • 比较次数题:不要只数“对齐轮数”,要把每一轮实际发生的字符比较相加。原书 07 为 \(3+1+1+4=9\)。
  • nextval 优化题:相同字符造成的无效比较会被跳过,原书 08 从 9 次降为 \(3+4=7\) 次。
  • 滑动距离题:先按题目下标口径求表,再用
\[\text{右滑距离}=j-nextval[j].\]

原书 09 在第 6 个字符(0 下标 \(j=5\))失配,\(nextval[5]=3\),故右滑 \(5-3=2\) 位。

💡 熟练后也可直接对齐模拟,但必须保证“已匹配前缀能否与其后缀复用”的判断和 nextval 一致。

回源 · PDF 第 131 页 · b_131_20;PDF 第 131 页 · b_131_21;PDF 第 131 页 · b_131_22;PDF 第 132 页 · b_132_0;PDF 第 132 页 · b_132_1;PDF 第 132 页 · b_132_2;PDF 第 132 页 · b_132_3;PDF 第 132 页 · b_132_4;PDF 第 132 页 · b_132_5;PDF 第 132 页 · b_132_6;PDF 第 132 页 · b_132_7;PDF 第 132 页 · b_132_8;PDF 第 132 页 · b_132_9;PDF 第 132 页 · b_132_10;PDF 第 132 页 · b_132_11;PDF 第 132 页 · b_132_12;PDF 第 132 页 · b_132_13

2015 统考:第一次失配后,为什么是 \(i=5,j=2\)?

答案 C。 题目给出第一次失配时 \(i=j=5\),由此先判断它采用 0 下标。模式串 abaabc 的 next 为

\[(-1,0,0,1,1,2).\]

失配后 KMP 的动作是:主串指针 \(i\) 保持 5 不动,模式指针退到 \(next[5]=2\),所以新一轮从 \(i=5,j=2\) 开始。

⚠️ 看到选项中同时出现“\(i\) 是否加 1”和“\(j\) 退到哪里”,先用 KMP 的不变量排掉所有让 \(i\) 回退或擅自前进的选项。

回源 · PDF 第 132 页 · b_132_14;PDF 第 132 页 · b_132_15;PDF 第 132 页 · b_132_16;PDF 第 133 页 · b_133_0

2019 统考:KMP 到匹配成功共比较多少次?

答案 B,共 10 次。 模式串 abaabc 的 0 下标 next 为 \((-1,0,0,1,1,2)\)。

  • 第一轮连续比较 6 次,在主串、模式串的 5 号位失配;
  • 保持主串当前位置不动,把模式指针退到 \(next[5]=2\);
  • 第二轮再比较 4 次即匹配成功。

所以总次数为 \(6+4=10\)。

💡 这类题最稳的表格只记三列:当前 \(i\)、当前 \(j\)、本次比较后怎样更新。不要把“主串移动了几格”误当成“字符比较了几次”。

回源 · PDF 第 133 页 · b_133_1;PDF 第 133 页 · b_133_2;PDF 第 133 页 · b_133_3;PDF 第 133 页 · b_133_4

2024 统考:修正 next 数组下,最长能右滑多少位?

答案 A,最长右滑 5 位。 对模式串 aabaab 求 0 下标 nextval,并逐位计算

\[d_j=j-nextval[j].\]

各位置的滑动距离中,\(j=4\) 时 \(nextval[4]=-1\),因此 \(d_4=4-(-1)=5\),达到最大值。

⚠️ 题目问“某个字符失配时的最长距离”,必须逐位置比较 \(j-nextval[j]\);不能只看 nextval 的最小值,也不能把模式串长度直接当答案。

回源 · PDF 第 133 页 · b_133_5;PDF 第 133 页 · b_133_6;PDF 第 133 页 · b_133_7

综合应用题 01:next 定义中的 0、max 与“其他”分别为什么?

原书的三个问法,实际是在解释 next 的边界、最优跳转与兜底:

  1. \(j=1\) 时取 \(next[1]=0\):模式串首字符已经没有更短前缀可复用;若它失配,就让模式串右滑一位,并让主串转向下一个字符重新与模式首字符比较。
  2. 取 \(\max\{k\}\):满足前缀等于后缀的 \(k\) 可能不止一个。右滑距离是 \(j-k\),取最大的 \(k\) 就是取最小且不漏解的右滑距离;\(k\) 最大为 \(j-1\)。
  3. “其他”取 1:不存在可复用的非空相等前后缀,只能让模式首字符与主串当前字符再比较。

💡 本质:优先保留尽可能长的已匹配信息;完全无信息可保留时才退回模式首字符。

回源 · PDF 第 133 页 · b_133_9;PDF 第 133 页 · b_133_10;PDF 第 133 页 · b_133_11;PDF 第 133 页 · b_133_12

综合应用题 02(1):aabaac 的 next 数组怎样递推出来?

采用原书 1 下标口径,先置 \(next[1]=0,next[2]=1\)。之后每求 \(next[j]\):

  • 令 \(k=next[j-1]\);
  • 若 \(P[j-1]=P[k]\),则 \(next[j]=k+1\);
  • 若不等,就反复令 \(k=next[k]\);退到 \(k=0\) 仍不等时,令 \(next[j]=1\)。

对 aabaac 逐位得到

\[next=(0,1,2,1,2,3).\]

⚠️ 在 \(j=4\) 时要经历 \(k=2\to1\to0\) 的连续回退;这一步正是“递推算法”区别于凭感觉看前后缀的关键。

回源 · PDF 第 133 页 · b_133_13;PDF 第 133 页 · b_133_14;PDF 第 133 页 · b_133_15;PDF 第 133 页 · b_133_16;PDF 第 133 页 · b_133_17;PDF 第 134 页 · b_134_0;PDF 第 134 页 · b_134_2;PDF 第 134 页 · b_134_4;PDF 第 134 页 · b_134_6;PDF 第 134 页 · b_134_9;PDF 第 134 页 · b_134_10

综合应用题 02(2):aabaabaabaac 匹配 aabaac 为什么三趟成功?

沿用 \(next=(0,1,2,1,2,3)\):

  • 第一趟:前 5 个字符匹配,在 \(i=6,j=6\) 失配;令 \(j=next[6]=3\),\(i\) 不动。
  • 第二趟:从主串第 6 位与模式第 3 位继续,走到 \(i=9,j=6\) 再失配;仍令 \(j=3\)。
  • 第三趟:从该位置继续,最终完整匹配成功,匹配起点是主串第 7 位。

💡 画对齐图时只做两件事:失配处的主串字符固定;把模式串第 \(next[j]\) 位移到它下面。 这样不会误把 KMP 画成暴力回溯。

回源 · PDF 第 134 页 · b_134_11;PDF 第 134 页 · b_134_12;PDF 第 134 页 · b_134_13;PDF 第 134 页 · b_134_14;PDF 第 134 页 · b_134_15;PDF 第 134 页 · b_134_16;PDF 第 134 页 · b_134_17

【原书归纳总结与思维拓展】第4章

KMP 为什么能把最坏复杂度从 \(O(mn)\) 降到 \(O(m+n)\)?

暴力匹配低效的根源是:失配后让主串回溯,导致已确认相等的字符被反复比较。KMP 先分析模式串自身,为每个失配位置预计算最佳跳转:

  • 已匹配部分的后缀若等于模式串前缀,就把二者直接对齐;
  • 对齐部分无需重比,主串指针不回溯;
  • next 保存普通跳转,nextval 再消除“跳过去仍比较同一字符”的重复。

因此扫描主串和构造/使用模式信息都只需线性推进,整体按王道口径记为 \(O(m+n)\)。

回源 · PDF 第 135 页 · b_135_0;PDF 第 135 页 · b_135_1

怎样把 KMP 改成“统计主串中完整匹配次数”?

核心改动不是找到一次就返回,而是:

  1. 每当模式串全部匹配,计数器加 1;
  2. 为了不漏掉可重叠匹配,模式指针退回到能保留最长相等前后缀的位置;
  3. 主串继续向后扫描,直到结束。

⚠️ 若完整匹配后直接把模式指针清零,虽可能仍得到不重叠匹配,却会漏掉如 aaaa 中模式 aa 的重叠出现。统考通常不要求写完整 KMP 算法,但这道思维拓展能检验你是否真正理解“复用已匹配前后缀”。

回源 · PDF 第 135 页 · b_135_2;PDF 第 135 页 · b_135_3

【易混点】第4章成对概念对照

概念 A 概念 B 一句话切分
空串 空格串 长度 0 vs 长度 = 空格个数
串 线性表 操作以子串为单位 vs 以单个元素为单位
定长顺序存储 堆分配存储 编译期定长、会截断 vs 运行时 malloc
前缀 后缀 除最后一个字符的所有头部子串 vs 除第一个字符的所有尾部子串
PM 表 next 数组 next = PM 右移一位再加 1
next nextval 可能出现 pⱼ = p_{next[j]} 的无效回退 vs 消除了这种回退
暴力匹配 KMP 主串指针要回溯、最坏 O(nm) vs 主串指针不回溯、稳定 O(n+m)
王道 1 基约定 0 基、-1 哨兵约定 next[1]=0,j==0 后移 vs next[0]=-1,j==-1 后移;必须整套换算

【必背数字】第4章

数字 含义
n − m + 1 暴力匹配最多的趟数
O(nm) / O(n+m) 暴力匹配最坏 / KMP 时间复杂度
next[1] = 0,next[2] = 1 固定规则
next[j] = PM[j−1] + 1 手算捷径
右滑位数 = 已匹配字符数 − 对应 PM 值 KMP 核心公式
'abcac' → PM 00010,next 01112 教材标准例
'aaaab' → next 01234,nextval 00004 优化对照例
'ababa' → PM 00123 部分匹配值推导例
五个 最小操作子集(赋值、比较、求长、联接、求子串)

【口诀速记】第4章

维度 口诀 解释
next 手算 PM 右移一位再加一,首位补 0 next[j] = PM[j−1] + 1
两个固定值 next[1]=0,next[2]=1 不用推,直接写
编号约定 先看哨兵和失配分支,再做整体加减 1 基 j==0 与 0 基 j==-1 必须成套
右滑位数 已匹配字符数 − 对应 PM 值 PM=0 时滑得最远
nextval 撞脸就继承 pⱼ == p_{next[j]} 时,nextval[j] = nextval[next[j]]
KMP 本质 主串指针永不回溯 这是教材强调的核心机制与优势,比较次数题按此模拟
前缀后缀 前缀去尾,后缀去头 前缀是除最后一个字符的所有头部子串,后缀反之
回源 · PDF 第 121 页 · b_121_3;PDF 第 121 页 · b_121_4

【解题路线】第4章:KMP 题怎么下手

  • ① 手算 next 的最快路径:先写部分匹配值表 PM → 整体右移一位 → 每项加 1 → 首位置 0
  • ② 检查整套约定 → ⚠️ 看首项是 0 还是 -1,再看失配分支用 j==0 还是 j==-1;只有整套一致时,王道 next 才能整体减 1 换成 -1 哨兵数组
  • ③ 问 nextval:⚠️ 凡是 pⱼ == p_{next[j]} 的位置,nextval[j] 继承 nextval[next[j]];否则 nextval[j] = next[j]
  • ④ 问比较次数或指针变化:⚠️ 主串指针 i 永不回溯,失配时只把 j 退到 next[j],逐趟模拟即可
  • ⑤ 问右滑位数:已匹配字符数 − 对应的 PM 值

⚠️ 最容易错的两处:PM 表最后一位在转 next 时被舍去;next[1] = 0、next[2] = 1 是固定的,不用推。

⚠️ 零壹课件勘误(只作理解图,不照抄文字):aabaa 的前缀第三项应为 aab,不是 aa;课件 p47 顶部所列数组实为 nextval;“整体 +1”只是 -1 哨兵数组向王道 1 基数组的整套约定转换,不是“选项从 0 开始”的通用规则。

回源 · PDF 第 121 页 · b_121_5;PDF 第 121 页 · b_121_6