找出字符串中第一个匹配的下标
给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
如果使用暴力枚举,即用i遍历haystack数组,每次从i的位置开始比较,用j指针needle字符串,如果匹配失败就将j回退到neddle字符串开头,并且将i++,这种做法的时间复杂度为O(m*n),其中m是haystack的长度,n是needle的长度。
本题我们使用的是KMP算法实现模式串匹配判断。通过寻找needle和的最长相等前缀,每次匹配失败时,不直接将j指针回退到开头,而是通过前缀表不断递归回退到相等前缀的地方,判断当前字符是否相等,即先判断当前不相等位置的前面串是否存在与后缀相等的前缀,如果存在,则跳转到该串位置,再将haystack[i]与后一个位置neddle[j+1]进行比较,如果不相等,则继续跳转到与neddle[j+1]前面串后缀相等的前缀地方继续进行匹配
while (j>=0 && needle[j+1] != haystack[i])
{
j = next[j];
}
如果相等,从当前位置继续遍历即可。而这个需要回退的地方我们使用前缀表记录,即有多大最长长度的相等前后缀(前缀是指不包含最后一个字符且包含第一个字符的连续字符串,后缀是指不包含第一个字符且包含最后一个字符的连续字符串)。如果最后我们的j指针指向了needle数组最后的位置,即每个字符都匹配上了,说明haystack中存在模式串needle。
for (int i = 0; i < haystack.size(); i++)
{
while (j>=0 && needle[j+1] != haystack[i])
{
j = next[j];
}
if (needle[j + 1] == haystack[i])
{
j++;
}
if (j == needle.size() - 1)
{
return i - j ;
}
}
那么问题的关键就是如何求得前缀表,这里我们使用的前缀表next[j]是保存的需要跳转的j的位置,而需要比较的是跳转位置后面的位置,因此我们将j的初值和next[0]设置为-1,即当跳到j=-1时,我们比较j+1下标的元素也就是needle[0]。next[j]的后续元素实现方法和模式串匹配类似,我们从下标i=1开始,依次比较前后缀,如果相等,就将j++,然后将j赋值给next[i],说明这一段存在长度为j+1的最长相等前后缀,然后i后移,从之前的最长相等前后缀进行比较,如果当前元素(也就是前面子串的后缀的后一个位置)不同,则回退到前缀,比较前缀的后一个位置,如果依然不同,则将该前缀看成后缀,寻找该前缀的相等前缀(因此这个过程是递归的),最后j指针回退到-1或者能匹配上的第一个前缀末尾。如果匹配上,将j++,此时再将j的位置赋给next[i]。
int j=-1;
vector<int> next(needle.size());// abababab
next[0] = -1;
for (int i = 1; i < needle.size(); i++)
{
while (j >= 0 && needle[j + 1] != needle[i])
{
j = next[j];
}
if (needle[j + 1] == needle[i])
{
j++;
}
next[i] = j;
}
完整代码:
class Solution {
public:
int strStr(string haystack, string needle) {//needle是子串
int j=-1;
vector<int> next(needle.size());// abababab
next[0] = -1;
for (int i = 1; i < needle.size(); i++)
{
while (j >= 0 && needle[j + 1] != needle[i])
{
j = next[j];
}
if (needle[j + 1] == needle[i])
{
j++;
}
next[i] = j;
}
for(int i=0;i<next.size();i++)
cout << next[i];
j = -1;
for (int i = 0; i < haystack.size(); i++)
{
while (j>=0 && needle[j+1] != haystack[i])
{
j = next[j];
}
if (needle[j + 1] == haystack[i])
{
j++;
}
if (j == needle.size() - 1)
{
return i - j ;
}
}
return -1;
}
};
重复的子字符串
给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
KMP算法:通过求得s字符串的next数组,s字符串的长度-s字符串的最大相等前后缀(即next[s.size()-1]的值)所剩下的子串就是要求得的重复子串。如果该子串长度能被s字符串长度整除,说明能由它的一个子串重复多次构成。
假设s="ababababababab"
那么a="ababababababxx"
那么b="xxabababababab"
证明过程:a是相等前缀,b是相等后缀,x表示该位置为空。a[0],a[1]=b[0],b[1]=a[2],a[3]=b[2],b[3]=...=a[s.size-2],a[s.size-3]
代码如下:注意结尾判断条件,当最长相等前后缀长度为0时也应该判为false。
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int j = -1;
vector<int> next(s.size());// abababab
next[0] = -1;
for (int i = 1; i < s.size(); i++)
{
while (j >= 0 && s[j + 1] != s[i])
{
j = next[j];
}
if (s[j + 1] == s[i])
{
j++;
}
next[i] = j;
}
for (int i = 0; i < next.size(); i++)
cout << next[i];
return next[s.size() - 1] != -1 && s.size() % (s.size() - next[s.size() - 1] - 1) == 0;
}
};