前几天面试的时候,被问了一个字符串匹配的问题,没写出来,痛定思痛,决定好好地恶补一番。
问题:两个字符串 A 和 B,A 的长度大于 B 的长度,问:A 中是否含有 B,若有,有几个。
暴力匹配太 low 了,我不想写,kmp 又写不出来,所以当时就直接放弃了。都快被自己菜哭了。
言归正传,先求 next 数组。
为了方便,next 数组的下标设置从 1 开始。next[i] 表示 前 i 个字符中匹配的前缀与后缀的数目 +1(加1,是代表下一次要匹配的前缀字符,当然你也可以选择不+1)。
那么如何求 next[i] 呢?next[i] 要求的是前 i 个字符中匹配的前后缀的个数再 +1,已经知道前面 i-1 个字符匹配的数目+1(=next[i-1] ), 那么,若第 i 个字符 == next[i-1] (待匹配的前缀字符),那么 next[i] = next[i-1] + 1,否则,对next[i-1]再取next。
为什么要这样做呢?
将 next[i-1] 设为 p(代表前 i-1 个字符中已匹配的前后缀数目 +1),next[next[i-1]] 即为 next[p] 。当前字符对匹配失败,那么需要再找匹配的前后缀,再看相应字符对是否匹配。怎么找?next[p] 为前 p 个字符匹配的前后缀+1,那么next[p] 对应的字符的前面的字符串就是 前 p-1个字符的前缀(前后缀匹配),那么其必然是 第 i-1 个字符的前缀(因为 p = next[i-1] 是 前 i-1 个字符的前缀匹配数目+1)。即字符串的前缀的前缀是该字符串的前缀(理解这句话就懂我的意思了)。
好吧,我也觉得我没有说清楚,这个没有图真的很难说。。。而且我个人觉得将next 直接设为匹配的前后缀数目其实更好。暂时先这样了,抱歉,以后再改进。
在寻找子字符串的时候,若不匹配,移动的是 要匹配的那个字符串(较短的那个)。
代码写得也不好,先将就看一下吧。
我必须承认现在的我还是太菜,真的要努力了。
// next 数组存储已匹配元素个数+1,即下一个要匹配的前缀元素下标(下标从 1 开始),
// 匹配:以当前字符作为后缀的最后一个字符与相应前一个已经匹配成功的前缀(next[i-1])的后一个字符进行匹配,
// 若两个字符不相等,则再找前一个已经匹配的前缀
vector<int> getNext(string needle){
//已匹配的个数+1,下标从 1 开始
vector<int> next(needle.size() + 1, 0);
//cout << next.size();
next[1] = 1;
for(int i = 2; i <= needle.size(); ++i){
int p = next[i - 1];
while(p > 1 && needle[i-1] != needle[p - 1]){
p = next[p-1];
}
if(needle[i-1] == needle[p - 1])
p += 1;
next[i] = p;
}
for(int i = 0; i < next.size(); ++i){
cout << next[i] << ' ';
}
return next;
}
int strStr(string haystack, string needle) {
if(needle.size() == 0){
return 0;
}
if(haystack.size() == 0){
return -1;
}
vector<int> next = getNext(needle);
int i ;
int j = 0;
for(i = 0; i < haystack.size(); ++i){
//+1是要下标对应,-1是与next的含义有关
while(j > 0 && needle[j] != haystack[i]){
j = next[j+1-1] - 1;
}
if(needle[j] == haystack[i]){
++j;
}
if(j == needle.size()){
return i - needle.size() + 1;
}
}
return -1;
}