2023年3月8日
8 第四章 字符串
今日任务
● 344.反转字符串
● 541. 反转字符串II
● 剑指Offer 05.替换空格
● 151.翻转字符串里的单词
● 剑指Offer58-II.左旋转字符串
详细布置
344.反转字符串
【链接】(文章,视频,题目)
建议: 本题是字符串基础题目,就是考察 reverse 函数的实现,同时也明确一下 平时刷题什么时候用 库函数,什么时候 不用库函数
题目链接/文章讲解/视频讲解:代码随想录
【第一想法与实现(困难)】双指针,直接干
【看后想法】swap用法
【实现困难】无
【自写代码】
class Solution {
public:
void reverseString(vector<char>& s) {
// 双指针
for (int left = 0, right = s.size() - 1; left < right; left++, right--) {
std::swap(s[left], s[right]);
}
}
};
【收获与时长】
541. 反转字符串II
【链接】(文章,视频,题目)
建议:本题又进阶了,自己先去独立做一做,然后在看题解,对代码技巧会有很深的体会。
题目链接/文章讲解/视频讲解:代码随想录
【第一想法与实现(困难)】暴力规则
【看后想法】
-
规则可以简化,只要>=k,就将前k个翻转;只要小于k,剩余全部翻转
-
可以直接使用std::reverse函数
-
std::reverse(s.begin()+i, s.begin()+i+k)); // 注意左闭右开。下标是i~i+k-1的k个元素被翻转了。
-
同理,std::reverse(s.begin(), s.end())等价于std::reverse(s.begin(), s.begin() + s.size()),翻转了下标为0, size-1的size个元素,也就是所有元素。
-
【实现困难】
【自写代码】
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i+=(2*k)) {
// 如果剩余字符大于或等于 k 个,则反转前 k 个字符
if (s.size() - i > k) {
std::reverse(s.begin() + i, s.begin() + i + k);
} else { // 如果剩余字符少于 k 个,则将剩余字符全部反转。
std::reverse(s.begin() + i, s.end());
}
}
return s;
}
};
【收获与时长】半小时
【链接】(文章,视频,题目)
剑指Offer 05.替换空格
建议:对于线性数据结构,填充或者删除,后序处理会高效的多。好好体会一下。
题目链接/文章讲解:代码随想录
【第一想法与实现(困难)】暴力遍历,erase, 反序insert
【看后想法】
-
暴力的方法时间复杂度较高,每次erase, insert都会有O(n)时间复杂度,因为全部后续元素都要重新排列。总体时间复杂度是O(nm),相当于平方时间
-
如果要避免平方时间,可以用双指针方法,从后往前重新赋值。只要在新列表做一次后序的重新赋值即可
【实现困难】
【自写代码】
class Solution {
public:
string replaceSpace(string s) {
// 双指针,后序遍历
// 左指针,旧字符串位置,判断是否空格。右指针,新字符串位置
// 先找出空格数目
int count = 0;
int old_size = s.size();
for (int i = 0; i < old_size; i++) {
if (s[i] == ' ') {
count++;
}
}
s.resize(s.size() + 2 * count);
int new_size = s.size();
for (int left = old_size - 1, right = new_size - 1; left < right; left--, right--) {
if (s[left] != ' ') {
s[right] = s[left];
} else {
s[right - 2] = '%';
s[right - 1] = '2';
s[right] = '0';
right-=2;
}
}
return s;
}
};
【收获与时长】半小时
151.翻转字符串里的单词
【链接】(文章,视频,题目)
建议:这道题目基本把 刚刚做过的字符串操作 都覆盖了,不过就算知道解题思路,本题代码并不容易写,要多练一练。
题目链接/文章讲解/视频讲解:代码随想录
【第一想法与实现(困难)】
-
暴力,从后往前找单词,拼接到字符串中
-
无法做到原地修改,空间复杂度不行
-
很多细节需要处理
【看后想法】
-
两件事情分开做,代码会比较清晰
-
原地去除多余字符串
-
反转单词 = 反转字符串再将单词正回来
-
【实现困难】
-
原地删除多余空格,最后指针位置是空格或者串尾slow = max index + 1,而max index = size - 1,所以resize(slow)。size = slow;
-
反转字符串中间部分,注意左闭右闭,左边++,右边–
-
在完美空格的字符串中找单词,i是一个分割符号,空格或者串尾i = size
【自写代码】
class Solution {
public:
string reverseWords(string s) {
// 先原地删除多余空格(参考原地删除数组元素的快慢指针)
// 再反转字符串,回正单词
std::cout << "s:" << s << std::endl;
removeExtraSpaces(s);
std::cout << "removeExtraSpaces, s:" << s << std::endl;
std::reverse(s.begin(), s.end());
int start = 0;
for (int i = 0; i <= s.size(); i++) {
if (i == s.size() || s[i] == ' ') { // 串尾或者空格,i是分隔符
reverseBetweenIndex(s, start, i - 1); // 左闭右闭
start = i + 1;
}
}
return s;
}
// 原地删除多余空格
void removeExtraSpaces(string &s) {
// int fast = 0; // 快指针,原来字符串下标,长,未删除
int slow = 0; // 慢指针,新字符串下标,段,已删除
for (int fast = 0; fast < s.size(); fast++) {
if (s[fast] != ' ') { // 非空格才工作,删除所有空格
if (slow != 0) { // 非第一个单词,补充空格
s[slow++] = ' ';
}
// 复制单词,直到空格或者串尾
while (fast < s.size() && s[fast] != ' ') {
s[slow++] = s[fast++];
}
}
}
// 最后,slow与fast对应原字符串的空格或串尾,也就是slow = max_index + 1
s.resize(slow);
}
// 左闭右闭,反转字符串中间部分
void reverseBetweenIndex(string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
std::swap(s[i], s[j]);
}
}
};
【收获与时长】2小时,综合,比较有难度,适合后续再练手!
剑指Offer58-II.左旋转字符串
【链接】(文章,视频,题目)
建议:题解中的解法如果没接触过的话,应该会想不到
题目链接/文章讲解:代码随想录
【第一想法与实现(困难)】直接申请新的字符串,暴力先放后半段,再放前半段
【看后想法】
- 字符串中不使用额外空间的做法,优先考虑反转子串
【实现困难】三个reverse,没有的难度,关键是思路
【自写代码】
class Solution {
public:
string reverseLeftWords(string s, int n) {
// 不使用额外空间。考虑子串反转
std::reverse(s.begin(), s.begin() + n);
std::reverse(s.begin() + n, s.end());
std::reverse(s.begin(), s.end());
return s;
}
};
【收获与时长】
20分钟,反转子串有点意思。