28.找出字符串中第一个匹配的下标
分析:刚看到题目的时候懵了一下,仔细想了一下还是有思路
思路一:遍历第一个字符串,当第一个字符串中的第一个字符和第二个字符串中的第一个字符相等时,如果第一个字符串当前字符后面的字符数大于等于第二个字符串的长度时,可以进行区间判断。
class Solution {
public:
bool judge(string mid,string neddle,int start,int end)//传入区间
{
int index=0;
//cout<<end;
while(start<=end)
{
if(mid[start++]!=neddle[index++])
return false;
}
return true;
}
int strStr(string haystack, string needle) {
//思路:嵌套for循环
int m=haystack.size(),n=needle.size();
for(int i=0;i<m;i++)
{
if(haystack[i]==needle[0])//当第一位相同时
{
if(m-i+1>=n)//当剩下得数还可以遍历needle时
{
if(judge(haystack,needle,i,i+n-1))
return i;
}
}
}
return -1;
}
};
二刷:
思路一:截取字符串
class Solution {
public:
bool judge(string s1,string s2){
int n=s2.size();
for(int i=0;i<n;i++){
if(s1[i]!=s2[i]) return false;
}
return true;
}
int strStr(string haystack, string needle) {
int n=haystack.size(),m=needle.size();
for(int i=0;i<n;i++){
if(haystack[i]==needle[0]){
string mid=haystack.substr(i,m);//截取字符串
if(judge(mid,needle)) return i;//找到时直接返回
}
}
return -1;
}
};
思路二:KMP算法
- 1.求出匹配字符串的next数组
- 2.使用这个next数组对要匹配的字符串进行操作,通过对指针的移动减少重复对比的次数
class Solution {
public:
void trans(int *next,string &mid){
int j=0;
next[0]=0;
for(int i=1;i<mid.size();i++){
while(j>0 && mid[i]!=mid[j]) j=next[j-1];
if(mid[i]==mid[j]) j++;
next[i]=j;
}
}
int strStr(string haystack, string needle) {
//思路二:KMP算法
//1.获取needle的next数组
if(needle.size()==0) return 0;
int next[needle.size()];
trans(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.重复的子字符串
分析:刚开始想到了字符串匹配的方法,但是没有实现成功
看了卡哥的几个KMP视频,感觉明白了又有点懵,明天再自己写一遍试试
思路:KMP算法
1. 只要获得next数组,就能确定该数组是不是有相同子数组组成!
2.首先要获得next数组,创建空的长度为s.size()的数组;
next[0]必然是为0的,因为第一个元素的前缀不存在,使用变量 j 作为与遍历元素相比较元素的下标,使用i来遍历元素;
j 必然初始为0,因为第一个元素的前缀不存在,自然不存在可以比较的数;
i初始为1,因为从第2个元素开始才存在前缀;
3.用变量 i 对字符串 s 进行遍历,考虑两种情况
(1)当 i 指向的元素等于 j 指向的元素时,i 和 j 要同时自增
(2)当两个指针指向的元素不相等时,要把 j 指针指向next数组中第 j-1 位置的值,因为next数组中第 j-1 位置的值代表了 j-1位置的最长相等前缀(就是前后缀的最大相等字符串),这样就可以直接避免无意义的回退;此处需要使用while循环,因为回退不是一次性,而是要回退到当 j 指向的元素等于 i 指向的元素或者 j 为0的状态。
一次遍历后要对 next 数组中的当前 i 位置赋值,毫无疑问当前位置的最长相等前后缀就是 j 的值,因为 j 下标指向的值和 i 下标指向的值相等或者 j =0,j 前面的元素是和 i 遍历过来相等的或者不存在,所以 next[i] = j ;
4.上述是获取next数组的过程,在主函数中,我们调用上述过程的到next数组,next 数组中存储的是最大相等前后缀,所以next数组中最后一位一定是最大的,判断next数组中最后一位是否等于0,如果不为0即存在重复元素,再判断next数组的长度能不能被最大相等前后缀整除完,如果可以则代表这个字符串是由重复子字符串组成。
5.分析:为啥 j 回退的时候 i 不会退呢?为啥 j 不回退到 0 呢?
???
没来得及复习前面的知识,之前KMP算法很久之前学过搞忘了,确实感觉这个知识点开始难了起来,找时间完善一下KMP算法的图解吧,以及分析的感觉不太清晰。
二刷:
思路:KMP
- 1.求出next数组
- 2.只要是重复子字符串构成的字符串,分两种情况
- 子字符串有重复字符:必然减去最长相等前后缀后,剩下的能被字符串整除
- 子字符串没有重复字符:减去最长相等前后缀后,剩下的必然是一个完整的子字符串,必然能被字符串整除
- 子字符串有重复字符:必然减去最长相等前后缀后,剩下的能被字符串整除
class Solution {
public:
void getNext(int *next,string mid){
int n=mid.size();
int j=0;
next[0]=0;//初始化
for(int i=1;i<n;i++){
while(j>0 && mid[i]!=mid[j]) j=next[j-1];//此时j回退
if(mid[i]==mid[j]) j++;//相同元素j自增
next[i]=j;//j赋值为最大相等前后缀
}
}
bool repeatedSubstringPattern(string s) {
int n=s.size();
if(n==1) return false;
int next[n];
getNext(next,s);//得到next数组
if(next[n-1]!=0 && n%(n-next[n-1])==0) return true;//最后一位最长相等前后缀不为0
return false; //并且s长度减去最长相等前后缀之后能被n整除
}
};
剑指offer48.最长不含重复字符串的子字符串
思路:滑动窗口+unordered_set
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//思路一:unordered_set插入,存在即更新且重新加入
//思路二:双指针+unordered_set
int n=s.size();
if(n<=1) return n;
int left=0,right=0;
int res=0;
unordered_set<int>visted;
while(right<n){
while(visted.find(s[right])!=visted.end()){//有重复值
res=max(res,right-left);//更新最大值
visted.erase(s[left++]);
}
visted.insert(s[right++]);
}
int midres=visted.size();//最后没有更新的子字符串
res=max(res,midres);
return res;
}
};
二刷:
思路一:滑动窗口
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n=s.size();
int left=0,right=0;
int res=0;
unordered_set<int>set;
while(right<n){
while(left<right && set.find(s[right])!=set.end()){//遇到相同值时左边界滑动
set.erase(s[left]);
left++;
}
set.insert(s[right++]);//右边界增加
if(set.size()>res) res=set.size();//更新最大长度
}
return res;
}
};