1.反转字符串 344
思路:双指针,一个从数组开头往后走,一个从数组结尾往前走,挨个互换元素即可。
class Solution {
public:
void reverseString(vector<char>& s) {
for(int i=0, j=s.size()-1; i<s.size()/2; i++,j--)
{
swap(s[i],s[j]);
}
}
};
库函数reverse也可以直接实现,但本题要考察如何手动反转,所以不要用库函数。
2. 反转字符串② 541
当需要固定规律一段一段去处理字符串的时候,要想想在for循环的表达式上做文章。
比如这里就是让i每次移动2k个。
class Solution {
public:
string reverseStr(string s, int k) {
for(int i=0; i<s.size(); i=i+(2*k)) //一段一段的,就在循环条件上做功夫,让i每次移动2k个
{
//1.每隔2k个字符,反转前k个字符
//2.剩余字符小于2k但大于等于k个,反转前k个字符,其余不变
if(i+k<=s.size())
{
reverse(s.begin()+i,s.begin()+i+k);
//i第一次是0,第二次是2k...所以反转第一次是0-k之间,第二次是2k-3k之间
}
//3.剩余字符小于k个,则剩余字符全部翻转
else
{
reverse(s.begin()+i,s.end());
}
}
return s;
}
};
3.替换空格 剑指offer 05
思路:
1.先统计字符串中空格的个数;
2.将数组填充到原数组中每个空格被替换后新数组的大小;
3.然后利用双指针法,一个从新的数组末尾开始,一个从旧的数组末尾开始,从后向前替换空格。
class Solution {
public:
string replaceSpace(string s) {
int count=0; //统计空格个数
int oldSize=s.size();
for(int i=0; i<oldSize; i++)
{
if(s[i]==' ')
{
count++;
}
}
//扩充数组长度
s.resize(s.size()+count*2);
int newSize=s.size();
//双指针法,从后向前替换
for(int i=oldSize-1, j=newSize-1; i<j; i--,j--)
{
if(s[i]!=' ')
{
s[j]=s[i]; //原来的元素不是空格的话,就要腾地方,所以把这一位往后放
}
else //原来的元素是空格的话,没有要挪动的元素,而后面的长度已经扩充好了,所以直接填数即可
{
//s是一个字符串,所以填充元素要用单引号引起来
s[j]='0';
s[j-1]='2';
s[j-2]='%';
j=j-2; //填充好后新的下标直接往前跳两个,不要担心会重复,因为下一轮循环开始前j还要--的
}
}
return s;
}
};
时间复杂度:O(2n)=O(n)
如果是从前向后填充的话,每填充一位都要将它后面的元素向后移动,有两层for循环,所以时间复杂度是O(n^2)
4.反转字符串里的单词 151
思路:先整体翻转,再局部翻转。
(1)移除多余空格;
(2)将整个字符串反转;
(3)将每个单词反转。
第一步:移除多余空格。
使用双指针法,慢指针组成移除空格后的数组,快指针遍历当前数组。
void removeExtraSpace(string& s){
//用双指针去掉多余的空格
int slowIndex=0, fastIndex=0;
//去掉字符串前面的空格,此时只动快指针即可
//之所以能去掉字符串开头空格,是因为fast从0开始
while(s.size()>0 && fastIndex<s.size() && s[fastIndex]==' ')
{
fastIndex++; //移动到不为0的地方为止
}
//去掉字符串中间的空格,这时慢指针就要派上用场了
//fast-1>0用来保证起码第一个元素不是空格
//s[fastIndex-1]==s[fastIndex]表示连续两个都是空格
for(; fastIndex<s.size(); fastIndex++)
{
if(fastIndex-1>0 && s[fastIndex]==' ' && s[fastIndex-1]==s[fastIndex])
{
continue; //连着两个都是空格的话,直接退出当前循环,让fast+1,重新开始,就是往后走了一步
}
else
{
s[slowIndex]=s[fastIndex]; //没有连续空格的话,就要把fast赋给slow,组成新数组
slowIndex++;
}
}
//去掉字符串末尾的空格
//因为slow用来组成新字符串,所以判断末尾元素用slow即可
//s[slowIndex-1]是因为经过上面的循环,slow最后已经指向末尾元素的下一个了
if(slowIndex-1>0 && s[slowIndex-1]==' ')
{
s.resize(slowIndex-1); //是空格的话,resize就不要最后一个了
}
else
{
s.resize(slowIndex);
}
}
第二步:将整个字符串反转。
void reverse(string& s, int start, int end)
{
for(int i=start, j=end; i<j; i++,j--)
{
swap(s[i],s[j]);
}
}
第三步:
先调用前两个函数,实现相应的功能。
再将单词挨个翻转。
完整代码:
class Solution {
public:
//将整个字符串翻转
void reverse(string& s, int start, int end)
{
for(int i=start, j=end; i<j; i++,j--)
{
swap(s[i],s[j]);
}
}
void removeExtraSpace(string& s){
//用双指针去掉多余的空格
int slowIndex=0, fastIndex=0;
//去掉字符串前面的空格,此时只动快指针即可
//之所以能去掉字符串开头空格,是因为fast从0开始
while(s.size()>0 && fastIndex<s.size() && s[fastIndex]==' ')
{
fastIndex++; //移动到不为0的地方为止
}
//去掉字符串中间的空格,这时慢指针就要派上用场了
//fast-1>0用来保证起码第一个元素不是空格
//s[fastIndex-1]==s[fastIndex]表示连续两个都是空格
for(; fastIndex<s.size(); fastIndex++)
{
if(fastIndex-1>0 && s[fastIndex]==' ' && s[fastIndex-1]==s[fastIndex])
{
continue; //连着两个都是空格的话,直接退出当前循环,让fast+1,重新开始循环,就是往后走了一步
}
else
{
s[slowIndex]=s[fastIndex]; //没有连续空格的话,就要把fast赋给slow,组成新数组
slowIndex++;
}
}
//去掉字符串末尾的空格
//因为slow用来组成新字符串,所以判断末尾元素用slow即可
//s[slowIndex-1]是因为经过上面的循环,slow最后已经指向末尾元素的下一个了
if(slowIndex-1>0 && s[slowIndex-1]==' ')
{
s.resize(slowIndex-1); //是空格的话,resize就不要最后一个了
}
else
{
s.resize(slowIndex);
}
}
//翻转单词
string reverseWords(string s) {
removeExtraSpace(s); //先去掉字符串的多余空格
reverse(s, 0, s.size()-1); //将整个字符串翻转
int start=0; //保证以上两步结束后开始下标在0
for(int i=0; i<=s.size(); ++i)
{
if(s[i]==' ' || i==s.size()) //到达空格或者字符串末尾,说明一个单词结束
{
reverse(s,start,i-1); //翻转单词
start=i+1; //更新start到下一个单词的开始
}
}
return s;
}
};
5.左旋转字符串 剑指offer58②
思路:局部翻转+整体翻转
(1)翻转前n的字符串;
(2)翻转n到末尾的字符串;
(3)翻转整个字符串。
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(),s.begin()+n);
reverse(s.begin()+n,s.end());
reverse(s.begin(),s.end());
return s;
}
};
6.实现strStr() 28 (到后面就不懂了)
KMP(字符串匹配)主要应用在字符串匹配上。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
前缀表:
前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串的下标应该跳到哪个位置。
什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。把各子串的最长相同前后缀的长度,组成对应前缀表。
字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。
如何利用前缀表找到当字符串不匹配时指针应该移动的位置?
找到模式串和文本串不匹配的位置,看模式串当前字符的前一个字符的前缀表的数值是多少(因为要找前面字符串的最长相同前后缀);如果前一个字符的前缀表的数值是i,就把模式串下标移动到下标i的位置继续比较。
时间复杂度:n为文本串长度,m为模式串长度,时间复杂度是O(n+m)。
前缀表与next数组:
next数组可以是前缀表,但很多实现是把前缀表统一减一之后作为next数组。
构造next数组其实就是计算模式串s的前缀表的过程。 主要有如下三步:
7.重复的子字符串 459
判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。
在判断 s + s 拼接的字符串里是否出现一个s的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
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;
}
};
本题的KMP做法没懂。