一、反转字符串
class Solution
{
public:
void reverseString(vector<char> &s)
{
// 双指针
int left = 0;
int right = s.size() - 1;
while (left < right) // 相等没要交换了
{
swap(s[left++], s[right--]);
}
}
};
二、反转字符串II
541.反转字符串II
读题感觉被坑了!表述的很不清楚!
它这个意思是对整个字符串s,每2k个字符中,前k个进行反转。相当于是分组反转的那个意思。
class Solution
{
public:
// 自己实现的reverse是 左闭右闭 的
// 库函数实现的reverse是 左闭右开 的
void reverse(string &s, int left, int right)
{
while (left < right)
{
swap(s[left++], s[right--]);
}
}
string reverseStr(string s, int k)
{
for (int i = 0; i < s.size(); i += 2 * k)
{
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. k<= 剩余字符数 < 2k,则反转前 k 个字符
if (i + k <= s.size())
{
reverse(s, i, i + k - 1);
continue; // continue用的很牛逼
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
reverse(s, i, s.size() - 1);
}
return s;
}
};
// 等价的不要continue的写法
class Solution
{
public:
// 自己实现的reverse是 左闭右闭 的
// 库函数实现的reverse是 左闭右开 的
void reverse(string &s, int left, int right)
{
while (left < right)
{
swap(s[left++], s[right--]);
}
}
string reverseStr(string s, int k)
{
for (int i = 0; i < s.size(); i += 2 * k)
{
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. k<= 剩余字符数 < 2k,则反转前 k 个字符
if (i + k <= s.size()) // 为啥加这个判断? 为啥要取得等于 <= ?
{
reverse(s, i, i + k - 1);
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
else // i+k >s.size() ,说明剩下的字符数不足k个
{
reverse(s, i, s.size() - 1);
}
}
return s;
}
};
1、if(i+k <= s.size()) // 为啥加这个判断? 为啥要取得等于 <= ?
因为不加这个判断的话,reverse(s,i,i+k-1),这里剩余的字符不足k个,是不能对i到i+k进行逆置的。 取等号的临界条件,其实可以画个特例图,例如 “abc”,k=3。
2、为啥continue呢?
因为对于尾部的不足k个处理,我们写在下面了。
三、替换空格
剑指Offer05.替换空格
不要借助额外的空间,使用O(1)的空间复杂度。
代码随想录详解
双指针解法:从后往前,替换空格,i
指向新长度的末尾,j
指向旧长度的末尾。
直到两个指针 相遇或者是i
超过j
,结束!
class Solution
{
public:
string replaceSpace(string s)
{
// 1.先统计空格的数量
int countspace = 0;
int oldsize = s.size();
for (auto e : s)
{
if (e == ' ')
countspace++;
}
// 2.扩容s
s.resize(oldsize + countspace * 2);
// 3.定义双指针 从后向前替换
int newsize = s.size();
int i = newsize - 1;
int j = oldsize - 1;
while (j < i)
{
// 如果i遇到空格 ,那么开始替换
if (s[j] == ' ')
{
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2;
}
else
{
s[i] = s[j];
}
i--;
j--;
}
return s;
}
};
四、[有点难]翻转字符串里的单词
这个题比较麻烦的就是,给的字符串有前导空格,中间连续空格,结尾连续空格。
题目还要求把多余的空格给删除了。我们要用O(1)的空间复杂度。
大体思路:整个大字符串整体反转,然后对于每个单词再进行反转,就可以得到目标字符串了!
参考我写的这篇移除元素的双指针解法:我写的 27.移除元素
利用双指针移除多余空格的操作:要求不使用库函数,空间复杂度为O(1)
fast
快指针:指向我们想要获取的元素
slow
慢指针:指向快指针获取元素所指向的新的位置(这个位置依旧在原字符串中)
class Solution
{
public:
void reverse(string &s, int left, int right)
{
// 左闭右闭的写法 [ ]
while (left < right)
{
swap(s[left++], s[right--]);
}
}
void removeSpace(string &s)
{
int fast = 0;
int slow = 0;
for (; fast < s.size(); fast++)
{
// 符合我们要收集的字母,条件:fast指向不为空
if (s[fast] != ' ')
{
// 细节处理:我们还需要保留单词之间的一个空格,
// 但是除了首个单词,我们是直接放在slow指向的位置的
if (slow != 0) // 特判一下,当slow不是指向第一个起始位置的时候
{
s[slow++] = ' '; // slow慢指针所指向的位置,手动添加一个单词间的空格
}
// 正常的将fast指向的值赋值给慢指针指向的值
// 需要持续赋值使用while
while (fast < s.size() && s[fast] != ' ')
{
s[slow++] = s[fast++];
}
}
}
s.resize(slow);
}
string reverseWords(string s)
{
removeSpace(s);
reverse(s, 0, s.size() - 1);
int start = 0; // 记录每个单词的开始下标
for (int i = 0; i <= s.size(); i++)
{
// 分割每个单词,找到每个单词的结尾
if (i == s.size() || s[i] == ' ')
{
reverse(s, start, i - 1);
start = i + 1; // 更新下一个单词的开始下标start
}
}
return s;
}
};
五、左旋转字符串
string substr(size_t pos = 0, size_t len = npos) const;
// 我第一眼想到的解法
class Solution
{
public:
string reverseLeftWords(string s, int k)
{
// 可以分为两次子串截取,然后再将两个子串拼接起来
return s.substr(k, s.size() - k) + s.substr(0, k);
}
};
// 多次逆置也可以
// 先逆置前部分,后逆置后部分,最后整体逆置即可
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;
}
};
第一种解法使用了 substr 函数进行子串截取,但 substr 函数返回的是一个新的 std::string 对象,需要使用额外的内存来存储生成的子串。然而,在 C++ 中,std::string 的拷贝操作会隐式地调用复制构造函数,因此,原字符串和子串共享底层的字符数组,并不会额外占用内存空间。