344.反转字符串
题目链接:344.反转字符串
本题也算是双指针法的一个经典应用了。
有序数组的平方,三数之和,四数之和也是两个指针逐步往中间移动。
可以自己做一个总结:双指针法的一个经典的移动过程。
我们应该头尾各有2个指针,这两个指针用来指向我们要进行两两交换的位置,然后这两个指针同时向中间移动。
代码思路:
len = nums.size();
使用for循环交换字符:
你可能会想:为什么是
i < len;,而不是i <= len;?A:遇到这种边界性的条件,想不明白时,代入一个具体的例子就能想懂了。
for (int i = 0; j = len - 1; i < len / 2; i++, j--) {
}
为什么没有写 j 移动的终止条件?
A:因为 i 和 j 是同时移动的,我控制了 i ,相对来说就控制了 j
接下来就是交换2个元素,我们可以直接调用 swap()。
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]);
}
}
};
541.反转字符串Ⅱ
题目链接:541.反转字符串II
与上一道题目是一脉相承的,只不过用到了上道题中反转字符串里面的实现的函数。
本题没有涉及到具体的算法,就是模拟在这个复杂规则下,让我们去反转字符串。
很多同学写这道题的代码很复杂,其实可以很简洁。
针对这道题,很多人写for循环很习惯这么写:
for (i = 0; i < s.size(); i++)
其实,你会发现这道题每次处理的时候都是以2k个、2k个、2k个……去遍历字符串,因此没必要写 i++, 而应该写 i += 2k;, 直接让 i 每次移动2k,然后每次操作里面的前k个。(即操作 i 到 i + k 这样距离的一个字符串,进行反转)
代码思路:
i 每次移动2k,那么每次翻转就是 i 到 i + k 这样的距离。
for (i = 0; i < s.size(); i += 2k) {
reverse(s, i, i + k);
}
reverse() 实现的是:针对 s 这个字符串,从 i 开始,到 i + k 这个距离进行反转。
i ~ i + k,不包含 i + k ,因此是左闭右开区间。
一般来说,编程语言自己实现的库函数都是左闭右开规则。
(大家自己实现reverse函数的时候,可以左闭右开,也可以左闭右闭)
🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢🦢
在反转字符串的时候,有一个条件:
因为
i每次移动2k个,那么移到最后的这一块不够k个该怎么办?这个时候如果还直接对
i到i + k这个距离进行反转的话,就会操作到空的字符串了。
因此需要加一个判断条件: if (i + k < s.size()) ,条件成立,再进行反转。
要保证 i + k 在数组的范围内,不要超过这个数组的长度。
for (i = 0; i < s.size(); i += 2k) {
if (i + k < s.size()) {
reverse(s, i, i + k); //反转时不包含i + k
}
}
⚠️边界条件要注意!
细节拷问:应该写
if (i + k < s.size())还是if (i + k <= s.size())?A:代一个例子就可以知道了 07:30
注:reverse() 里面,反转时不包含 i + k
因此:
for (i = 0; i < s.size(); i += 2k) {
if (i + k <= s.size()) {
reverse(s, i, i + k); //反转时不包含i + k
continue;
}
reverse(s, i, s.size());
}
有些同学把这道题目想复杂了,因为尾部处理的时候,还分凑足 k 个和凑不足 k 个这2种情况;
其实凑足 k 个的情况在if语句里面顺带处理了;没凑足 k 个的情况在外层 reverse() 处理了。
AC代码: (核心代码模式)
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
//包含2种情况:
//1.每隔2k个字符的前k个字符进行反转
//2,剩余字符 < 2k,但>= k个,则反转前k个
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k);
}
else {
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
我们可以自己写一个 reverse() ,左闭右闭区间:
AC代码: (核心代码模式)
class Solution {
public:
//实现自己的reverse函数
void reverse(string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
//包含2种情况:
//1.每隔2k个字符的前k个字符进行反转
//2,剩余字符 < 2k,但>= k个,则反转前k个
if (i + k <= s.size()) {
reverse(s, i, i + k - 1);
continue;
}
reverse(s, i, s.size() - 1);
}
return s;
}
};
总结: 关键的思路:我们要想清楚在操作字符串或操作数组的时候,如果发现让我们一段一段地(2k)去处理,
那么这里面的i是可以成段成段地跳的。(没有必要老选择 i++ ,摒弃固性思维)
剑指offer 05.替换空格
题目链接:剑指offer 05.替换空格
AC代码: (核心代码模式)
class Solution {
public:
string replaceSpace(string s) {
int count = 0;
int sOldSize = s.size();
//统计空格个数
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') {
count++;
}
}
//扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
s.resize(s.size() + count * 2);
int sNewSize = s.size();
//从后向前将空格替换成"%20"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
if (s[j] != ' ') {
s[i] = s[j];
}
else {
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2;
}
}
return s;
}
};
151.翻转字符串里的单词
题目链接:151.反转字符串中的单词
难度指数:😀😕🙁
动画:
整体思路: 将原字符串进行整体反转,反转之后,再对每个单词进行一次反转,这样就可以得到目标字符串。
复杂的地方在于:需要把多余的空格都删掉。
在数组章节里,"移除元素"那道题讲了在数组中如何移除元素,
那么本题要求是移除多余的空格,空格也属于元素,因此思路是一样的。
移除空格,可能很多同学会这么写:
for () {
if (空格) {
erase(); //删除
}
}
erase() 的时间复杂度是O(n)
那么这整个移除操作的时间复杂度是O(n^2)
正常来说,对这个字符串进行移除元素,只需要O(n)就可以达到效果。
因为很多同学调用
erase()只是删除一个元素,而这里相当于批量删除。
思路都是使用双指针:快指针,慢指针
- 快指针指向我们想要获取的元素
- 慢指针指向我们获取快指针获取的元素所指向的新的位置。
本题的空间复杂度控制在O(1),不申请一个新的数组。
快指针用来获取符合题目要求的字母;慢指针告诉我们获取这些字母之后,更新在哪里。
代码思路:
slow一开始也是指向起始位置,
fast一定是要把字符串全遍历一遍。
找到不为空的字符,把它收集起来,然后赋给慢指针 slow 所指向的地方。 (慢指针告诉我们更新在哪里)
⚠️注意:把多余的空格移除的时候,需要保留单词之间的空格。(第一个单词前面不需要留空格) 这个逻辑需要特殊处理:
if (slow != 0) { //slow不指向第一个位置的时候 s[slow] = ' '; slow++; }
当慢指针不是起始位置的时候,慢指针所指向的位置要多留一个空格。
slow = 0;
for (fast = 0; fast < s.size(); fast++) {
if (s[fast] != ' ') { //如果这里的字符不为空,就把它收集起来
if (slow != 0) { //slow不指向第一个位置的时候
s[slow] = ' ';
slow++;
}
}
}
接下来,如果快指针不为空,应该把快指针指向的单词直接赋给慢指针:
slow = 0;
for (fast = 0; fast < s.size(); fast++) {
if (s[fast] != ' ') { //如果这里的字符不为空,就把它收集起来
if (slow != 0) { //slow不指向第一个位置的时候
s[slow] = ' ';
slow++;
}
while (fast < s.size() && s[fast] != ' ') {
s[slow] = s[fast];
fast++;
slow++;
}
}
s.resize(slow);
}
s.resize(slow);
因为我们是原地修改字符串,那么此时新的字符串就是 slow 所指向的位置。
AC代码: (核心代码模式)
class Solution {
public:
//实现自己的reverse函数
void reverse(string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
//去除所有空格并在相邻单词之间添加空格
void removeExtraSpaces(string& s) {
int slow = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] != ' ') {
if (slow != 0) { //slow不指向第一个位置的时候
s[slow++] = ' ';
}
}
while (i < s.size() && s[i] != ' ') {
s[slow++] = s[i++];
}
}
s.resize(slow);
}
string reverseWords(string s) {
removeExtraSpaces(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;
}
}
return s;
}
};
剑指offer 58.Ⅱ 左旋转字符串
AC代码: (核心代码模式)
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;
}
};