344.反转字符串
考察 reverse 函数的实现,同时明确刷题时什么时候用库函数,什么时候不用
题目
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s
的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
提示
-
1 <= s.length <= 10(5)
-
s[i]
都是 ASCII 码表中的可打印字符
思路
首尾交换,次首尾交换,次次首尾再交换,两个指针不断往中间移动
代码实现
-
定义长度 len = num.size()
-
for 循环 i 指向头,j 指向尾,i 走一半长度停止
- swap
class Solution {
public:
void reverseString(vector<char>& s) {
int len = s.size();
for(int i = 0, j = len-1; i < j; i++,j--){
swap(s[i], s[j]);
}
}
};
541. 反转字符串II
题目
给定一个字符串 s
和一个整数 k
,从字符串开头算起,每计数至 2k
个字符,就反转这 2k
字符中的前 k
个字符。
-
如果剩余字符少于
k
个,则将剩余字符全部反转。 -
如果剩余字符小于
2k
但大于或等于k
个,则反转前k
个字符,其余字符保持原样。
示例:
输入:s = "abcdefg", k = 2
输出:"bacdfeg"
提示:
-
1 <= s.length <= 10(4)
-
s
仅由小写英文组成 -
1 <= k <= 10(4)
思路
按题意操作遍历就好,但注意 for 循环里不要 i++,i + 2k 更快
代码实现
- for 循环遍历 s,i 每次移动 2k
- if 判断 i+k <= s.size(),再进行反转,不然就操作空字符串了(如果不好判断是小于还是小于等于,就带特定值去尝试一下)
- reverse(s, i, i+k):针对 s,从 i 开始,到 i+k 段的距离进行反转(一般是左闭右开原则,不包含 i+k)
- continue;
- 尾部反转:reverse(s, i, s.size())
- if 判断 i+k <= s.size(),再进行反转,不然就操作空字符串了(如果不好判断是小于还是小于等于,就带特定值去尝试一下)
class Solution {
public:
string reverseStr(string s, int k) {
for(int i = 0; i < s.size(); i += (2 * k)){
if(i + k <= s.size()){
reverse(s.begin() + i, s.begin() + i + k);
continue;
}
reverse(s.begin() + i, s.end());
}
return s;
}
};
剑指Offer 05.替换空格
对于线性数据结构,填充或删除,后序处理会更高效
题目
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
示例:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
思路
首先扩充数组长度到每个空格替换成"%20"之后的大小
然后用双指针法,从后向前替换空格
i 指向新长度的末尾,j 指向旧长度的末尾
j 遇到空格时,i 填充 “%20”,其他 j 遇到什么,i 填充什么
从后向前填充的好处:
-
不用申请新数组
-
从后向前填充元素,避免了从前先后填充元素,每次添加元素都要将之后的所有元素往后挪(从前向后填充时间复杂度 O( n 2 n^2 n2)
代码实现
-
定义 count = 0,统计空格数量
-
定义 sOldSize 为现在 s 的字符串长度
-
for 循环遍历 s,统计空格数量
-
s.resize,扩充字符串
-
定义 sNewSize
-
for 循环遍历,i 起始位置为 sNewSize-1,j 起始位置为 sOldSize -1
- if 判断不是空格,则 j 是什么 i 填充什么
- 是空格,则 i、i -1、i-2 填充 “%20”
-
return s
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.resize(s.size() + count * 2);
int sNewSize = s.size();
for(int i = sNewSize - 1, j = sOldSize - 1; i > j; i--, j--){
if(s[j] != ' ')s[i] = s[j];
else{
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2;
}
}
return s;
}
};
-
时间复杂度:O(n)
-
空间复杂度:O(1)
拓展:字符串与数组有什么差别
字符串是若干字符组成的有限序列,也可以理解为是一个字符数组。
在 C 语言中,把一个字符串存入一个数组时,也把结束符 ‘\0’ 存入数组,以此作为该字符串是否结束的标志,如下面代码
char a[5] = "asd";
for (int i = 0; a[i] != '\0'; i++) {
}
在 C++ 中,提供一个 string 类,string 类会提供 size 接口,可以用来判断 string 类字符串是否结束,不需要 ‘\0’ 来判断是否结束,如下面代码
string a = "asd";
for (int i = 0; i < a.size(); i++) {
}
而 vector 和 string 在基本操作上没有区别,但是 string 提供了更多的字符串处理接口,例如 string 重载了 +,而 vector 没有。所以想处理字符串,还是会定义一个 string 类型
151.翻转字符串里的单词
题目
给你一个字符串 s
,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例:
输入:s = "the sky is blue"
输出:"blue is sky the"
提示:
-
1 <= s.length <= 10(4)
-
s
包含英文大小写字母、数字和空格' '
-
s
中 至少存在一个 单词
**进阶:**如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1)
额外空间复杂度的 原地 解法。
思路
双指针移除空格,快指针获取符合题目要求的字母,慢指针告诉获取字母之后,更新在哪里
不适用 erase,时间复杂度是 O(n),再用 for 循环的话,时间复杂度就是 O( n 2 n^2 n2) 了
将原字符串进行一个整体的反转(reverse),反转之后再对每一个单词里面再进行一次反转
代码实现
- for 循环,快指针指向开始,遍历一遍 s
- 如果快指针指向不为空格
- 如果 slow != 0(慢指针不指向第一个位置时),把慢指针的位置赋给空格,慢指针 ++,保证除了第一个单词,每个单词的前面留一个空格
- while 快指针 < s.size() 且 快指针不为空格,快指针指向的单词赋给慢指针,快指针++,慢指针++
- s.resize(slow)
- 如果快指针指向不为空格
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 slow = 0;
for(int i = 0; i < s.size(); i++){
if(s[i] != ' '){
if(slow != 0)s[slow++] = ' ';
while(i < s.size() && s[i] != ' '){
s[slow++] = s[i++];
}
}
}
s.resize(slow);
}
string reverseWords(string s) {
removeExtraSpace(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;
}
};
我的易错
我卡在思考下面代码这一步上太久了,老师视频里解读是:“慢指针不是起始位置的时候,慢指针指向的位置要多留出来一个空格,慢指针再加加,后面遇到的单词就可以继续再给慢指针所指向的地方赋值了”,但我实在是被那一堆 ++ 操作绕晕了
但最后回看代码随想录的时候,发现第三个 if 是 while,又想通了(
为了避免下次又卡住,记录一下我的理解:
- for 循环的 ++i,是为了下面的 if 判断快指针指向如果是空格的话,就跳过空格
- if(s[i] ! = ’ ')
- if (slow != 0) s[slow++] = ’ ’ 意思得整体来看,下面的 while 是直接把快指针指向的整个单词循环遍历给慢指针,整个单词赋值之后,下一次 slow 就留一个空格,并且 ++
- 所以这里 slow 的 ++ 和 i 的 ++,主要一次性赋值整个单词,赋值后
s[i] != ' '
判断是空格的话,就退出循环了 -
while (i < s.size() && s[i] != ' ') { s[slow++] = s[i++];}
- if(s[i] ! = ’ ')
注:for 循环里 i++ 和 ++i 运行结果一样,但 ++i 不会有一个临时变量
剑指Offer58-II.左旋转字符串
题目
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
限制:
1 <= k < s.length <= 10000
难度提升:
- 不能申请额外空间,只能在本串上操作
思路
自己想了蛮久,想不出怎么在本串操作,看了解析,妙啊!
-
反转区间为前 n 的子串
-
反转区间为 n 到末尾的子串
-
反转整个字符串
代码实现
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;
}
};