下面是模式串 t,上面是目标串 s。蓝色的模式串代表匹配上了,红色代表没有匹配上。
前面都一样,直到 s[5]!=t[5].
那么暴力解法是:
可以看到又从 s[1] 开始重新匹配了,虽然一开始就匹配不上,又要让 s[2] 和 t[0] 开始匹配。
但是通过观察可以这样匹配:
不需要让 s[1] 和 t[0] 匹配。直接将模式串 t 的第一个字符搬到 s[3] 处。
理由如下:
先看这个图,也就是本文第一张图:
蓝色是匹配上的部分,那么 s[0:4] 就等于 t[0:4] 了。而且 t[0:1] 和 t[3:4] 相同。那么 t[0:1] 就和 s[3:4] 相同。所以可以直接把 t[0] 搬到 s[3] 下面,t 是整体右移的。然后继续比较 t[1] 和 s[4]。
那任务就是找到 t 要搬移多少距离了。就是大家说的 next 数组咋整。
先直接上 next 数组:
next[i] 的含义是 t 的 [0:i] 个字符里前缀和后缀最大能匹配的长度-1。注意前缀不包括最后一个,后缀不包括第一个。下面是怎么得出 next 数组:
- next[0] 约定为 -1.因为前缀不能包括最后一个,后缀不能包括第一个。
- t[1] != t[0],后面不用看了,直接 next[1] = -1.
- t[m:2] != t[0:n],后面不用看了,直接 next[2] = -1.(m>0,2-m=n-0,下面一样)
- t[3:3] == t[0:0],直接 next[3] = 0.
- t[3:4] == t[0:1],直接 next[4] = 1.
- 找不到 t[m:5] == t[0:n],直接 next[5] = -1.
以 next[4] 为例,它表示 t[0:4] 里前缀和后缀最大能匹配的长度-1。t[3:4] == t[0:1],那么匹配的长度就是 2,长度减 1 就是 1.那么 next[4] = 1.
到 next[5] 的由来是:因为 t[3:4] == t[0:1] 了,就继续观察 t[5] 和 t[2], 这两个不一样,说明长度不能是 3.那么长度可不可能是 2 呢?也就是 t[4:5] 和 t[0:1] 是不是一样?那就看看嘛。看看 t[0] 和 t[4] 是不是一样。也不一样。可以匹配的长度也不可能是 2.同理长度也不是 1.所以 next[5] = -1.前面的过程需要一步步倒退。就是代码里的 while 循环。
代码是下面的样子:
int j = -1;
vector<int> next(t.size());
next[0] = -1;
for (int i = 1; i < t.size(); i++) {
while (j >= 0 && t[i] != t[j + 1]) {
j = next[j];
}
if (t[i] == t[j + 1]) {
j++;
}
next[i] = j;
}
代码很短,但是之前我一行的不能理解,自己推一下就好受许多了。