对于一类字符串匹配问题:
给定长度分别为 m , n m,n m,n 的字符串 A , B A,B A,B,给出两个相同长度的字符串匹配的定义,要求找出 A A A 在 B B B 中所有匹配的位置。
可能能用如下方式解决:
定义匹配函数 C ( a , b ) C(a,b) C(a,b),使得完全匹配函数 P ( i ) = ∑ j = 0 m − 1 C ( A j , B i + j ) P(i)=\sum_{j=0}^{m-1}C(A_j,B_{i+j}) P(i)=∑j=0m−1C(Aj,Bi+j) 等于 0 0 0 当且仅当 A A A 和 B [ i . . i + m − 1 ] B[i..i+m-1] B[i..i+m−1] 匹配。
然后,我们将 C ( a , b ) C(a,b) C(a,b) 拆成若干个有关 a , b a,b a,b 的乘积,即 C ( a , b ) = ∑ k = 1 K f k ( a ) g k ( b ) C(a,b)=\sum_{k=1}^K f_k(a)g_k(b) C(a,b)=∑k=1Kfk(a)gk(b)。然后我们对每个 k k k 计算它对 P ( i ) P(i) P(i) 的贡献,即计算 P k ( i ) = ∑ j = 0 m − 1 f k ( A j ) g k ( B i + j ) P_k(i)=\sum_{j=0}^{m-1}f_k(A_j)g_k(B_{i+j}) Pk(i)=∑j=0m−1fk(Aj)gk(Bi+j)。这是减法卷积,可以优化至 O ( n log n ) O(n\log n) O(nlogn)。
总时间复杂度 O ( K n log n ) O(Kn\log n) O(Knlogn),一般来说 K K K 是常数。
举几个例子吧:
-
一般的字符串匹配问题。
一个想法是设 C ( a , b ) = a − b C(a,b)=a-b C(a,b)=a−b,但这只对单个字符生效,对 P ( i ) P(i) P(i) 是不一定生效的,比如 "ab" \texttt{"ab"} "ab" 和 "ba" \texttt{"ba"} "ba"。
正确的做法应该是设 C ( a , b ) = ( a − b ) 2 C(a,b)=(a-b)^2 C(a,b)=(a−b)2,这样两个字符串一旦出现某个位置不同, P ( i ) P(i) P(i) 就一定大于 0 0 0。
-
带通配符 # \texttt{\#} # 的字符串匹配问题。
上一题的扩展,直接设 C ( a , b ) = [ a ≠ # ] [ b ≠ # ] ( a − b ) 2 C(a,b)=[a\neq \texttt{\#}][b\neq \texttt{\#}](a-b)^2 C(a,b)=[a=#][b=#](a−b)2 即可。