28. 实现 strStr()
28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
kmp算法理解上还是有难度,其实真要理解起来还好,但是next数组的代码实现还是不太好想,这个视频可以很好的帮助理解。最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibili。结合代码随想录节具体步骤和代码实现,会帮助很好的理解KMP算法。代码随想录
class Solution {
public:
void getNext(int* next,string& s){
next[0]=0;
int j=0;
for(int i=j+1;i<s.size();i++){
while(j>0&&s[i]!=s[j]){ //一定要先判断不相等的情况,因为一旦不相等j的值就会改变
j=next[j-1];
}
if(s[i]==s[j]){
j++;
}
next[i]=j;
}
}
int strStr(string haystack, string needle) {
if(needle.size()==0) return 0;
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;
}
};
459.重复的子字符串
暴力解法:首先我觉得暴力解法也不太好理解,不过在纸上推一遍还好;
class Solution {
public:
bool repeatedSubstringPattern(string s) {
if(s.size()==1) return false;
string tem="";
string str="";
for(int i=0;i<s.size()/2;i++){
tem+=s[i];
while(str.size()<s.size()){
str+=tem;
if(str.compare(s)==0&&str.size()==s.size()) return true;
}
str="";
}
return false;
}
};
重点就是理解tem和str的作用,tem是重复的子串,如果由多个tem组成的str长度和内容都与s相等,那么s就可以由它的一个子串重复多次构成。
tmp
字符串:
tmp
最初是一个空字符串(""
)。- 在for循环中,逐个字符地将输入字符串
s
的字符追加到tmp
中(tmp += s[i]
)。 tmp
的作用是表示一个可能的重复子串。在每次迭代中,它都会从输入字符串s
的开头增长一个字符。
str
字符串:
str
最初是一个空字符串(""
)。- 在while循环中,程序将
tmp
的内容重复追加到str
,直到str
的长度等于输入字符串s
的长度。 str
的作用是检查由tmp
表示的子串的重复是否可以形成原始字符串s
。- 如果在任何时刻
str
等于s
并且它们的长度相同(str.compare(s) == 0 && str.size() == s.size()
),则函数返回true
,表示s
可以通过重复tmp
表示的子串构建。
移动匹配
思路:将s变为s+s,例如abcabc变为abcabcabcabc;掐头去尾(不掐不就等于原来的s了吗)用新的字符串t存起来,如果t中包含s那么s就可以由它的一个子串重复多次构成。可以参考下面的文章,证明了这么做的充分性和必要性。
周期字符串问题(两种方法) | 春水煎茶 - 王超的个人博客 (writings.sh)
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) return true; // //如果不等于npos说明能在可能的位置找到s
return false;
}
};
npos是一个常数,表示size_t的最大值(Maximum value for size_t)。许多容器都提供这个东西,用来表示不存在的位置,类型一般是std::container_type::size_type。
KMP法:
首先对于next数组的求法就不多说了。这道题里面如果next数组的最大值(即子串最大相等前后缀不为0),并且字符串长度与最长相等前后缀之差可以被字符串长度整除,那么就说明有重复的子字符串。
class Solution {
public:
void getNext(int* next,const string& s){
next[0]=-1;
int j=-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;
}
}
bool repeatedSubstringPattern(string s) {
if(s.size()==0) return false;
int next[s.size()];
getNext(next,s);
int len=s.size();
if(next[len-1]!=-1&&len%(len-(next[len-1]+1))==0) return true; //如果next数组的最大值(即子串最大相等前后缀不为0),并且字符串长度与最长相等前后缀之差可以被字符串长度整除,那么就说明有重复的子字符串
return false;
}
};
还有一点,关于为什么next[len-1]一定是最大相等前后缀,是因为如果s是由重复子串构成,因为next在读完s的第一个子串之后,next[i]的值会一直增加直到填满next数组。(图示next数组是经过-1操作之后)其实我更倾向于不减的,比较直观好理解一点。