在我之前的一篇文章中详细解读了String Matching的算法。下面是对KMP算法实现的代码:
再来简要回顾一下算法的思路:
-
算每一个状态 i 的影子向状态 shadow[i]. (注意shadow[i] < i)
其中,shadow[i] = (text), where text = P[0,…,i-1], pattern = P[0,…,i-2]. (text) 算的是 length of the longest prefix of pattern which has common suffix with text. -
根据shadow表建立Finite State Machine
简要来说,假设已经到达了状态s,看到了新字符a。如果a是第s+1个字符,那么顺利向前推进到状态s+1。否则,回退到s的影子状态shadow[s](shadow[s]<s)影子状态看到a之后会走到哪里,s看到a之后就会走到哪里。 -
拿着FSM把Text扫描一遍
这一步就很简单了,扫描的时候每看到一个新字符(text[i]),就按照FSM上面走一步,记录走到的状态(table[i])。当走到最后一个状态时(table[i] = m),代表text[i-m+1,…,i] 是一个完全匹配。
整理完这题的思路复盘的时候发现其实每一步都是动态规划,都是根据当前状态和新看到的信息去获得新状态。
import java.util.*;
// KMP字符串匹配算法
public class Solution {
public String strStr(String haystack, String needle) {
if(needle.length()==0) {return haystack;}
// 用于表示FSM的数据结构。ArrayList的index代表上一个state,
// Character是看到的新字符,Integer是new state
ArrayList<Map<Character, Integer>> table = new ArrayList(needle.length()+1);
int[] shadow = new int[needle.length()+1];
// 其实可能会想到最后一个状态的影子状态已经没用了,为什么这里还要给它算?因为shadow会预先填好0和1的值,所以shadow表的length至少是2.
shadow[0] = 0; shadow[1] = 0;
// 必须先填好shadow[1].
// 因为如果放到循环里的话,第一个字符将会等于状态1的影子状态(0)的下一个字符,
// 于是shadow[1]就会被写成1,随后每个shadow[i]都会被写成i。
// Step1:建立shadow表
for(int i=2; i<=needle.length(); ++i){
// 不要弄混i和i-1。i代表状态i。needle的第i个字符的index是i-1。
// 状态i的影子状态(index是shadow[i-1]-1)的下一个字符(index是shadow[i-1])
if(needle.charAt(i-1)==needle.charAt(shadow[i-1])){
shadow[i] = shadow[i-1]+1;
}else{
int X = shadow[i-1];
while(true){
X = shadow[X];
if(needle.charAt(i-1)==needle.charAt(X)){
shadow[i] = X+1; break;
}
if(X==0) {
if(needle.charAt(i-1)==needle.charAt(0)){
shadow[i] = 1; break;}
else {shadow[i] = 0; break;}
}
}
}
}
// Step2:建状态转移图
for(int state=0; state<needle.length(); state++){
Map<Character, Integer> map = new HashMap();
for(char c='a'; c<='z'; ++c){
// 当前state的index是state-1,当前状态的下一个字符index是state
if(c==needle.charAt(state)){
map.put(c, state+1);
}else{
if(state==0) {map.put(c, 0); continue;}
// state0要单独考虑,因为这个时候table还是空的,没有可以回退的点
int pre_state = shadow[state];
int new_state = table.get(pre_state).get(c);
map.put(c, new_state);
}
}
table.add(map);
}
// Step3:遍历一遍haystack
int state = 0;
for(int i=0; i<haystack.length(); ++i){
state = table.get(state).get(haystack.charAt(i));
if(state==needle.length()) {
return haystack.substring(i-needle.length()+1, haystack.length());
}
}
return null;
}
}