_8LeetCode代码随想录算法训练营第八天-C++字符串
- 28.实现strStr()
- 459.重复的字字符串
28.实现 strStr()
KMP算法
什么是KMP
是由三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。
KMP有什么用
KMP主要应用在字符串匹配上。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。
什么是前后缀?
字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
如,对于aabaabaaf
其前缀包括:
a
aa
aab
aaba
aabaa
aabaab
aabaaba
aabaabaa
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
如,对于aabaabaaf
其后缀包括:
f
af
aaf
baaf
abaaf
aabaaf
baabaaf
abaabaaf
最长相等前后缀?
对于aabaaf,求最长相等前后缀
a 理论上没有前缀没有后缀;因为它是首字母继尾字母;最长相等前后缀是0
aa 前缀a;后缀a;最长相等前后缀是1
aab 前缀有a,aa;后缀有b,ab;因此最长相等前后缀为0
aaba 前缀有a,aa,aab;后缀有a,ba,aba;因此最长相等前后缀为1
aabaa 前缀有a,aa,aab,aaba;后缀有a,aa,baa,abaa;因此最长相等前后缀为2
aabaaf 前缀有a,aa,aab,aaba,aabaa;后缀有f,af,aaf,baaf,abaaf;因此最长相等前后缀为0
由此得到前缀表:[0,1,0,1,2,0]
如何使用前缀表匹配?
如,对于aabaabaaf,寻找匹配的aabaaf
首先,找到了aabaab
aabaaf此时不匹配了
就找除掉f的aabaa的最长相等前后缀为2,意味着有个后缀aa,还有个与其相等的前缀aa。
然后对于字符串aabaaf,就从下标为2的位置继续开始找。
next数组
当遇到冲突之后,next数组告诉我们需要回退到哪里,从新开始寻找。next数组其实就是前缀表。
如何求得next数组:
- 初始化next数组
- 处理前后缀不相同的情况
- 处理前后缀相同的情况
- 更新next数组的值
完整实现代码
Emmm ,有点吃力,水平不够,得多学学
/*
* @lc app=leetcode.cn id=28 lang=cpp
*
* [28] 找出字符串中第一个匹配项的下标
*/
// @lc code=start
class Solution {
public:
//next是指向最长相等前后缀数组的指针,s字符串是指需要查找的字符串
//s取引用是为了节省时间
//这个步骤挺简单的,但是不太想得明白,所以后面再多想想
//时间复杂度分析:n为文本串长度,m为模式串长度,此函数时间复杂度为m
void getNext(int* next, string& s)
{
int j = 0;//j指向前缀末尾位置,也指向包括i之前的子串的最长相等前后缀的长度,初始化为0
next[0] = 0;//初始化next[0]为0
//i从1开始的原因是下标为0的元素其next[0]就是0,不用再计算
for(int i = 1; i < s.size(); i++)
{
//处理前后缀不想同的情况
//j要保证大于0,因为有j-1的操作
while(j > 0 && s[i] != s[j])
j = next[j-1];//找j的前一个回退位置
//处理前后缀相同的情况
if(s[i] == s[j])
j++;
next[i] = j;
}
}
//时间复杂度分析:n为文本串长度,m为模式串长度,此函数时间复杂度为n
//因此整个时间复杂度为m+n,感觉也不太懂
int strStr(string haystack, string needle) {
int next[needle.size()];
getNext(next, needle);
int j = 0;
for(int i = 0; i < haystack.size(); i++)
{
while(j > 0 && haystack[i] != needle[j])
j = next[j - 1];
if(haystack[i] == needle[j])
j++;
if(j == needle.size())
return (i - needle.size() + 1);
}
return -1;
}
};
// @lc code=end
459.重复的子字符串
暴力解法
/*
* @lc app=leetcode.cn id=459 lang=cpp
*
* [459] 重复的子字符串
*/
// @lc code=start
class Solution {
public:
bool repeatedSubstringPattern(string s) {
//暴力解决
int length = s.size();//字符串的长度
if(length < 2)
return false;
//模拟所有可能字串的长度
for(int i = 1;i <= length/2; i++)
{
//串长度不是字串长度的倍数
if(length % i != 0)
continue;
string temp = s.substr(0,i);
string ans = "";
for(int j = 0; j < length/i; j++)
ans += temp;
if(ans == s)
return true;
}
return false;
}
};
// @lc code=end
移动匹配
所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
/*
* @lc app=leetcode.cn id=459 lang=cpp
*
* [459] 重复的子字符串
*/
// @lc code=start
class Solution {
public:
bool repeatedSubstringPattern(string s) {
//移动匹配的方法
string t = s + s;
t.erase(t.begin());
t.erase(t.end() - 1);
if(t.find(s) != std::string::npos)//这个判断搜索结果的std::string::npos我还没见过
return true;
return false;
}
};
// @lc code=end
KMP解法
重复字符串中的最小重复子串:
最长相等前后缀不包含的字串就是最小重复字串。
/*
* @lc app=leetcode.cn id=459 lang=cpp
*
* [459] 重复的子字符串
*/
// @lc code=start
class Solution {
public:
void getNext(int* next, string& s)
{
next[0] = 0;
int j = 0;
for(int i = 1; i < s.size(); i++)
{
while(j > 0 && s[i] != s[j])
j = next[j - 1];
if(s[i] == s[j])
j++;
next[i] = j;
}
}
bool repeatedSubstringPattern(string s) {
//KMP的方法---太牛啦
int size = s.size();
if(size == 0)
return false;
int next[size];
getNext(next, s);
//此处必须判断整个字符串的最大相等前后缀next[size - 1],如果next[size-1] == 0,就不成立
//同时,如果size不能整除next[size-1]也不行
if(next[size - 1] != 0 && size%(size - next[size - 1]) == 0)
return true;
return false;
}
};
// @lc code=end