经典字符串leetcode题
文章目录
- 经典字符串leetcode题
- 反转字符串
- 第一种方法:使用string里的库函数reverse
- 第二种方法:使用自己写的swap函数
- 第一种swap函数
- 第二种swap函数
- 151.翻转字符串里的单词
- 28. 实现 strStr()
- KMP算法
- 459.重复的子字符串
- 第一种:暴力法
- 第二种方法:我把它叫做双倍去头去尾法(移动匹配法)
- 第三种方法:kmp算法
反转字符串
反转字符串【简单】
第一种方法:使用string里的库函数reverse
给reverse函数传递vector迭代器的begin(第一个有效字符的下标)和end(最后一个有效字符的下标的下一个)—可以知道reverse函数是左开右闭的
class Solution {
public:
void reverseString(vector<char>& s) {
reverse(s.begin(),s.end());
}
};
第二种方法:使用自己写的swap函数
第一种swap函数
用i记录第一个元素的位置,j记录最后一个元素的位置,两个位置的元素交换后,i++,j–,两者往中间走
class Solution {
public:
void swap(vector<char>& s)
{
int i=0,j=s.size()-1;
while(i<j)
{
char tmp=s[i];
s[i]=s[j];
s[j]=tmp;
++i;
--j;
}
}
void reverseString(vector<char>& s) {
swap(s);
}
};
第二种swap函数
用按位异或^,按位异或二进制上的值:不同则为1,相同为0;那么利用这个机制两者相互按位异或会出现一个第三值,对这个第三值再次按位异或就能得到对方的值。
class Solution {
public:
void swap(vector<char>& s)
{
int i=0,j=s.size()-1;
while(i<j)
{
s[i]^=s[j];
s[j]^=s[i];
s[i]^=s[j];
++i;
--j;
}
}
void reverseString(vector<char>& s) {
swap(s);
}
};
151.翻转字符串里的单词
翻转字符串里的单词【中等】
思路:第一步:先跳过前面的空格 第二步:把单词依次往前面的位置填,遇到单词中间多余的空格则跳过,最后缩空间为依次一个单词一个空格一个单词这样的字符串第三步:字符串全部逆置第四步:把单词依次逆置回来
第一步和第二步动图:
class Solution {
public:
void reverse(string&s,int start,int end)
{
while(start<end)
{
swap(s[start++],s[end--]);
}
}
string reverseWords(string s) {
int fasti=0;
int slowi=0;
while(s.size()>0&&fasti<s.size()&&s[fasti]==' ')//去除前面的空格
{
fasti++;
}
for(;fasti<s.size();++fasti)
{
if(fasti-1>0&&s[fasti-1]==s[fasti]&&s[fasti]==' ')//遇到多余空格就跳过
//fasti-1>0是保证前一个位置有空格
{
continue;
}
else{
s[slowi++]=s[fasti];//把单词往前提
}
}
if(slowi-1>0&&s[slowi-1]==' ') //slowi-1>0是保证前一个位置有空格
{
s.resize(slowi-1);
}else
{
s.resize(slowi);
}
int start=0;
int end=s.size()-1;
reverse(s,start,end);//全部逆置
int i=0;
for(;start<=s.size();start++)//把单词逆置回来
{
if(s[start]==' '||start==s.size())
{
reverse(s,i,start-1);
i=start+1;
}
}
return s;
}
};
28. 实现 strStr()
实现 strStr()【中等】
KMP算法
这题可以用到KMP算法。这里介绍一下KMP算法。KMP算法主要运用在字符串匹配上,当有一个字串串和一个需要匹配的字符串,当子串出现不匹配时,可以根据已经匹配的一部分字符串内容去避免从头去匹配。
这里有一个字符串aabaaf
那么字符串的前缀就是不包括最后一个字符(f)的所有可能字符串:
a,aa,aab,aaba,aabaa。
字符串的后缀相应的就是不包括第一个字符串(a)的所有字符串:
f,af,aaf,baaf,abaaf。
公共前后缀是前后缀相等的部分。
比如字符串a,有前缀就没有后缀,有后缀就没有前缀。那么最大公共前后缀长度是0。
字符串aa,前缀为a,后缀为a,相等前后缀的长度是1
字符串aab,前缀a,aa,后缀b,ab,没有相等前后缀部分。
字符串aaba,前缀a,aa,aab,后缀a,ba,aba,最大公共前后缀长度是1即前缀为a,后缀为a的时候。
好了现在前后缀和公共前后缀及其长度介绍完了。那么现在介绍next数组。next数组就是对应字符串的每个字符对应的最大公共前后缀的长度。那么这些长度组成起来的就是一个前缀表。
假设字串aabaaf要匹配目标串aabaabaaf这个字符串,当遇到子串的f到目标串第二个b的时候,就看f的前一个字符的前缀表的数值。然后回退到该数值对应的字串的下标继续匹配—没错KMP算法就这么简单。(话说我之前遇到这种字符串匹配的时候也想到了回退到前面相等的部分再重新匹配,但是我不知道怎么写,这就是我和大佬之间的区别吧呜呜呜~)
KMP算法通过回退到前面相同的部分再次匹配而不是回退到开头再次匹配,减少了一部分消耗。
KMP算法匹配全过程:
接下来就是实现next数组(前缀表),有了前缀表才能实现kmp算法
实现前缀表有三步:初始化,前后缀处理不相等的情况,处理前后缀相等的情况。
第一步初始化:
这里定义两个指针:j指向前缀末尾的位置,i指向后缀末尾的位置
int j=0;
next[0]=j;//一开始公共前后缀长度为0,也就是j的值
第二步:前后缀处理不相等的情况:
那么要看前后缀相不相等,一开始i指向的位置就在j的后一个位置,然后遍历字符串串
for(int i=1;i<s.size();++i)
{
}
当遇到s[i]与s[j]不相等的情况,也就是遇到前后缀末尾不相等的情况,就要向前回退
next[j] (前缀表)记录着之前子串相等前后缀的长度
所以处理前后缀不相等情况:
首先j要大于0才能回退,就是最坏情况回退到0的位置,再回退就越界了,j回退到前缀表数值的下标
而且这是个不断回退的过程所以用while
while(j>0&&s[j]!=s[i])
{
j=next[j-1];
}
第三步处理前后缀相等的情况:
前后缀相等,那么指向前缀末尾的指针j就往后一步
if(s[i]==s[j])
{
j++;
}
next[i]=j;
最后整体next数组(前缀表)代码:
void getnext(int *next,const string&s)//开辟好next数组,也把s字符串传进来
{
int j=0;
next[0]=j;//初始化
for(int i=1;i<s.size();++i)
{
while(j>0&&s[j]!=s[i])//处理前后缀不匹配的情况
{
j=next[j-1];
}
if(s[j]==s[i])
{
j++;
}
next[i]=j;//无论前后缀相等还是不相等,i始终往后走,且前缀表一直在填充
}
}
next数组演示:
最后使用next数组(前缀表来匹配字符串)
i从0开始,遍历文本串—i指向的是文本串,j指向的是next数组(n[j])
int j=0;
for(int i=0;i<s.size();++i)
接下来是s[i]和n[j]的比较相等
如果s[i]与n[j]不相等,j就回退再重新匹配(回退前缀表的前一个数字对应的字串的下标位置)
while(j>0&&s[i]!=n[j])
{
j=next[j-1];
}
如果s[i]与n[j]相同,那么j++
if(s[i]==n[j])
{
j++;
}
如果j走到next数组的末尾(j==n.size())时,即是文本串里有子串—匹配成功,返回文本串第一个匹配的下标(比如文本串为:aabaabaaf,字串为aabaaf,匹配成功就返回文本串aabaabaaf的为3的下标)
if(j==n.size())
{
return (i-n.size()+1);
}
最后是使用next数组,用字串去匹配文本串的成体代码
int next[n.size()];
//n.size()是子串的大小,用子串的大小去创建next数组
int j=0;
for(int i=1;i<s.size();++i)
{
while(j>0&&s[i]!=n[j])
{
j=next[j-1];
}
if(s[i]==n[j])
{
++j;
}
if(j==n.size())
{
return (i-n.size()+1);
}
}
return -1;//如果不匹配就返回-1
题目代码:
class Solution {
void getnext(int*next,const string&s)
{
int j=0;
next[0]=j;
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;
}
}
public:
int strStr(string haystack, string needle) {
int next[needle.size()];
int j=0;
getnext(next,needle);
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 tmp="";
string str="";
for(int i=0;i<s.size()/2;++i)//取一半的s就能比较出
{
tmp+=s[i]; //每次都给tmp字符串增多一个s的字符
while(str.size()<s.size())
{
str+=tmp;//用重复的tmp字符串去和s字符串比较
if(str.compare(s)==0&&str.size()==s.size())//如果str字符串与s字符串相等且长度也相等就为true
{
return true;
}
}
str="";//记得要更新str
}
return false;
}
};
第二种方法:我把它叫做双倍去头去尾法(移动匹配法)
思路:把原字符串加倍后,去头再去尾,看现在的字符串里有没有原字符串,如果有则true,如果没有则为false
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string tmp=s+s;
tmp.erase(tmp.begin());//去头
tmp.erase(tmp.end()-1);//去尾
if(tmp.find(s)!=std::string::npos)//比较
{
return true;
}
return false;
}
};
第三种方法:kmp算法
假设字符串s由多个重复字串构成(重复字串为最小单位),重复字串的长度为x,所以s有n个x组成。
最长相等前后缀长度为m*x,所以n-m=1 【—》推理如下图
如果nx%[(n-m)x]=0,则可以判断出有重复出现的子字符串!
即如果数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。
首先要实现next数组(前缀表)然后判断len % (len - (next[len - 1] + 1)) == 0 即可(len为数组长度)
题目代码如下:
class Solution {
void getnext(int* next,const string&s)
{
int j=0;
next[0]=j;
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;
}
}
public:
bool repeatedSubstringPattern(string s) {
int next[s.size()];
getnext(next,s);
int len=s.size();
if(next[len-1]!=0&&len%(len-next[len-1])==0)
{
return true;
}
return false;
}
};