leetcode:Implement strStr() & KMP 算法
leetcode:Implement strStr() & KMP 算法
问题描述
实现strstr(),即字符串匹配。使用KMP算法解决。但是其实暴力求解也跟KMP算法的速度是相差无几的,因为目标串太短了
KMP算法与自动机
在编译原理的书上看见KMP算法的一种解释,将目的串转换成一个自动机,初始状态为0,假如目的串为helloworld,那么状态0接受'h'转换为状态1,状态1接受'e'转换为状态2,依次类推。
KMP算法的关键就是在某个状态x接收到不匹配的字符时,找到应该返回的状态,而不是每次都返回状态0(若返回状态0,则目标串指针向前走一步)。以一个数组保存状态1到状态n收到错误字符时应该返回的位置,如果达到状态n,则表示字符串已经被接收,所以最后一个状态不存在所谓的"接收到错误字符"。
vector<int> next(needle.size(), 0);
int t = 0;
for(int i = 2; i < needle.size(); ++i){
while(t > 0 && needle[t] != needle[i - 1]){
t = next[t];
}
if(needle[t] == needle[i - 1]){
next[i] = ++t;
}else{
next[i] = 0;
}
}
代码中以next[x]表示状态x接收到错误字符时应该返回的状态(该状态必须在x之 前,如果继续返回x状态,则有可能造成死循环,所以next[1] = 0 )并且当达到状态n时,表示字符串已经匹配,所以不需要求next[n]
代码中的t表示上一个状态接收错误字符时会返回的状态,i从2开始,所以t的初始值为next[1],也就是0。同时,t也可以表示上一个状态收到错误字符串时,满足如下条件的,最长的子串s的长度:
- s是目标串的真前缀
- s同时是已经匹配部分的真后缀
也可以理解为发生不匹配时,目标串应该向后走的步数。并且next[i]最多只会比next[i-1]大1,否则next[i-1]就不是最长的符合条件的s的长度。
为什么while循环里要有t=next[t]?执行到while循环入口时,已经到达了状态i,即已经接受了i个字符,也是说在到达状态i-1时,自动机以为自己接受到了正确的字符,所以到达了状态i,但是这个字符是不对的,因为继续收下去会出错,所以问题就变成了我们回到了状态状态next[i-1],也就是t的初始值,然后问题就变成了我们在状态t,并且收到了字符needle[i - 1],如果这个第i个字符是正确的,即needle[t] == needle[i-1]那么跳出while,同时next[i] = ++t,如果这个字符是错误的,继续往回找。因为s是已经匹配部分的真后缀,所以每次回溯都会是need[t]与next[i-1]比较。最后t==0时跳出循环,证明如果在状态i收到错误字符就只能重新匹配,源串指针向后滑动。即next[i] = 0,同时此时t的值也为0(跳出的条件就是t=0嘛)
完整AC代码如下:
class Solution {
public:
int strStr(string haystack, string needle) {
vector<int> next(needle.size(), 0);
int t = 0;
for(int i = 1; i < needle.size(); ++i){
while(t > 0 && needle[t] != needle[i - 1]){
t = next[t];
}
if(needle[t] == needle[i - 1]){
next[i] = ++t;
}else{
next[i] = 0;
}
}
int state = 0;
int pos = 0;
while(state != needle.size () && pos != haystack.size()){
if(haystack[pos] == needle[state]){
++state;
++pos;
}else if(!(state = next[state]) && haystack[pos] != needle[state]){
++pos;
}
}
if(state == needle.size()){
return pos - state;
}else{
return -1;
}
}
};
浙公网安备 33010602011771号