读者见过的 KMP 算法应该是,一波诡异的操作处理
pat
后形成一个一维的数组
next
,然后根据这个数组经过又一波复杂操作去匹配
txt
。时间复杂度 O(N),空间复杂度 O(M)。其实它这个
next
数组就相当于
dp
数组,其中元素的含义跟
pat
的前缀和后缀有关,判定规则比较复杂,不太好理解。
// 暴力匹配(伪码) intsearch(String pat, String txt){ int M = pat.length; int N = txt.length; for (int i = 0; i int j; for (j = 0; j if (pat[j] != txt[i+j]) break; } // pat 全都匹配了 if (j == M) return i; } // txt 中不存在 pat 子串 return -1; }
对于暴力算法,如果出现不匹配字符,同时回退
txt
和
pat
的指针,嵌套 for 循环,时间复杂度
O
(
M
N
)
,空间复杂度
O
(
1
)
。最主要的问题是,如果字符串中重复的字符比较多,该算法就显得很蠢。
比如 txt = "aaacaaab" pat = "aaab":
暴力算法
很明显,
pat
中根本没有字符 c,根本没必要回退指针
i
,暴力解法明显多做了很多不必要的操作。
KMP 算法的不同之处在于,它会花费空间来记录一些信息,在上述情况中就会显得很聪明:
kmp算法
再比如类似的 txt = "aaaaaaab" pat = "aaab",暴力解法还会和上面那个例子一样蠢蠢地回退指针
i
,而 KMP 算法又会耍聪明:
kmp算法
因为 KMP 算法知道字符 b 之前的字符 a 都是匹配的,所以每次只需要比较字符 b 是否被匹配就行了。
KMP 算法永不回退
txt
的指针
i
,不走回头路(不会重复扫描
txt
),而是借助
dp
数组中储存的信息把
pat
移到正确的位置继续匹配
,时间复杂度只需 O(N),用空间换时间,所以我认为它是一种动态规划算法。
KMP 算法的难点在于,如何计算
dp
数组中的信息?如何根据这些信息正确地移动
pat
的指针?这个就需要
确定有限状态自动机
来辅助了,别怕这种高大上的文学词汇,其实和动态规划的
dp
数组如出一辙,等你学会了也可以拿这个词去吓唬别人。
还有一点需要明确的是:
计算这个
dp
数组,只和
pat
串有关
。意思是说,只要给我个
pat
,我就能通过这个模式串计算出
dp
数组,然后你可以给我不同的
txt
,我都不怕,利用这个
dp
数组我都能在 O(N) 时间完成字符串匹配。
具体来说,比如上文举的两个例子:
txt1 = "aaacaaab" pat = "aaab" txt2 = "aaaaaaab" pat = "aaab"
publicintsearch(String txt){ int M = pat.length(); int N = txt.length(); // pat 的初始态为 0 int j = 0; for (int i = 0; i // 当前是状态 j,遇到字符 txt[i], // pat 应该转移到哪个状态? j = dp[j][txt.charAt(i)]; // 如果达到终止态,返回匹配开头的索引 if (j == M) return i - M + 1; } // 没到达终止态,匹配失败 return -1; }