一、LeetCode344.反转字符串
状态:已解决
1.思路
最开始本来想取个巧,直接从字符串最后一个字符开始,往前直接输出的,没想到这道题内部代码预置了一个输出函数,是从前往后输出的,因此只能修改原数组。(不推荐直接用reverse,如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。尤其是python、java)
其实这道题以前刷过,很自然地就能想到从两端开始向中间遍历,交换首尾两字符的解法,现在才知道这种解法叫双指针。想到这点基本上就没什么难度了,只是循环结束条件要稍稍想一下。我用的条件是i<j,因为i,j是同步移位的,如果是偶数长,i<j的条件下二者刚好能把数组遍历完,但如果是奇数长,最中间的元素遍历不到,但是最中间的元素也不需要做交换,正序反序都是它在中间,因此这个判断条件是合理。
2.代码实现
代码很简单。
class Solution {
public:
void reverseString(vector<char>& s) {
for(int i = 0,j = s.size()-1;i<j;i++,j--){
char temp = s[i];
s[i] = s[j];
s[j] = temp;
//或者直接swap(s[i],s[j]);
}
}
};
时间复杂度:O(n)
空间复杂度:O(1)
二、541. 反转字符串II
题目链接/文章讲解/视频讲解: https://programmercarl.com/0541.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2II.html状态:已解决
1.思路
大致思路还是跟上一题一样,只是外层多了一个区间用来分割小区间。
根据题意,我们可以得知三个区间:
设最外层遍历变量是start,字符串长度为n。
(1)若start + k > n,则说明剩余字符少于
k
个,则将剩余字符全部反转。(2)else if (start + 2*k > n),则说明剩余字符少于2*k,但大于等于k,依旧反转前k个字符。
(3)剩余字符多于2*k,也反转前k个。
反转部分的代码跟上一题一样。
2.代码实现
我的代码:
class Solution {
public:
string reverseStr(string s, int k) {
int start=0,n=s.size();
while(start<n){
if(start+k>n){
for(int i=start,j=n-1;i<j;i++,j--){
swap(s[i],s[j]);
}
}
else{
for(int i=start,j=i+k-1;i<j;i++,j--){
swap(s[i],s[j]);
}
}
start += 2*k;
}
return s;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
讲解代码:
直接用的reverse库函数。
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k );
} else {
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
三. 卡码网:54. 替换数字
题目链接/文章讲解:https://programmercarl.com/kamacoder/0054.%E6%9B%BF%E6%8D%A2%E6%95%B0%E5%AD%97.html
状态:已解决
1.思路
这道题太适合暴力了,即另开字符串,然后扫描,遇到小写字母就直接拼接到已有字符串,遇到数字就拼接“number”,实践证明暴力确实能过,但为了锻炼能力讲解用的还是双指针的方法。
(1)首先扩充数组到每个数字字符替换成 "number" 之后的大小。
例如 字符串 "a5b" 的长度为3,那么 将 数字字符变成字符串 "number" 之后的字符串为 "anumberb" 长度为 8。
(2)开始从后往前遍历:从前向后每次添加元素都要将添加元素之后的所有元素整体向后移动。(数组元素的增添)
其实很多数组填充类的问题,其做法都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
i指向新长度的末尾,j指向旧长度的末尾。
2.代码实现
(1)我的代码:
#include<iostream>
#include<string>
using namespace std;
int main(void){
string s,result;
cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]<'a'||s[i]>'z'){
result += "number";
}else{
result += s[i];
}
}
cout<<result;
return 0;
}
时间复杂度:O(n)
空间复杂度:O(n) //最坏情况长度为6*n.
(2)双指针法:
#include<iostream>
#include<string>
using namespace std;
int main(void){
string s;
cin>>s;
int n=s.size(),cnt=0;
for(int i=0;i<n;i++){
if('0'<=s[i]&&s[i]<='9'){cnt++;}
}
int fast=s.size()-1;//先让快指针指向原字符串的最后一个字符。
s.resize(s.size()+cnt*5);//“number”这个字符串替换原先的单个字符要多出5个单位空间。
int slow=s.size()-1;
while(slow>fast){
if('0'<=s[fast] && s[fast]<='9'){
s[slow-5] = 'n';
s[slow-4] = 'u';
s[slow-3] = 'm';
s[slow-2] = 'b';
s[slow-1] = 'e';
s[slow] = 'r';
slow-=6;
fast--;
}else{
s[slow--] = s[fast--];
}
}
cout<<s;
return 0;
}
时间复杂度:O(n)
空间复杂度:O(1)
(PS:做这道题的时候很搞笑地把不等式写成了 a<=x<=b 的形式,贻笑大方了,应该写成两个不等条件 a<=x && x<=b 才对。
四. 151.翻转字符串里的单词
题目链接/文章讲解/视频讲解:https://programmercarl.com/0151.%E7%BF%BB%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%87%8C%E7%9A%84%E5%8D%95%E8%AF%8D.html
状态:已解决
1.思路
最开始想的方法很复杂,准备用一个map存每个单词开始的位置和结束的位置,然后再开一个字符串,根据map的记录翻转单词后,存入新字符串中。但是这跟前面的训练关系不大,看了视频后才发现还有如此妙法。
思路:先进行字符串去空(只保留单词间的一个空格),然后翻转整个字符串,再根据空格局部翻转字符串。
(1)去空,可能很多人第一反应都是erase库函数,我的想法也是。但要先遍历整个字符串,然后对多的进行erase,而erase的时间复杂度依旧是O(n)了,外面再加一层遍历就是n^2的时间复杂度,不好。更优的解法是双指针,根据昨天的训练来看,双指针可以降低一个复杂度,去空操作就变成了删除指定元素的做法,跟之前数组删除元素一个道理,感兴趣的同学可以去看看数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili这个视频讲解。
(2)翻转字符串就是前面练的那些题,需要我们自己写翻转函数,前面练过了,这个操作也很简单。
2.代码实现
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 removeExtraSpaces(string& s){
int slow = 0,fast = 0;
while(fast<s.size()){
if(s[fast] != ' '){ //遇到非空格就代表有一个单词开头了,即删除所有空格
if(slow!=0) s[slow++] = ' ';//如果不是字符串的开头,就让slow先隔开一个空格
while(fast<s.size() && s[fast] != ' '){//开始填单词,遇到空格就停止,单词这个单词结尾了
s[slow++] = s[fast++];
}
}
else
fast++;
}
s.resize(slow);//以slow的大小作为去除多余空格后的大小。
}
string reverseWords(string s) {
removeExtraSpaces(s);
reverse(s,0,s.size()-1);
int start = 0;
for(int i=0;i<s.size();i++){
int left=0,right=0;
if(s[i]!=' '){//代表遇到单词了
left = i;//记录单词开始位置
while(s[i]!=' ' && i<s.size()){
i++;
}
right = i-1;//记录单词结束位置
reverse(s,left,right);//开始翻转
}
}
return s;
}
};
时间复杂度:O(n)
空间复杂度:O(1) //因为C++语言中字符串可变
五. 卡码网:55.右旋转字符串
题目链接/文章讲解:https://programmercarl.com/kama55.%E5%8F%B3%E6%97%8B%E5%AD%97%E7%AC%A6%E4%B8%B2.html
状态:已解决
1.思路
这道题是上一道题精华的变形,或者是极致应用,还是两轮反转,先把整个字符串翻转后,再分段翻转一次。
(1)我们先把字符串分成两段,第一段和第二段如图。
(1)第一次翻转:很明显两段顺序和我们要求的不一样,因此先把我们需要的尾部的子串给它提到前面来。
(2)此时两个子串的先后顺序已经正确了,但是各自的子串都是倒序,因此我们只需要再将两个子串分别翻转即可。
(两次反转,负负得正!)
2.代码实现
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(void){
string s;
int k;
cin>>k;
cin>>s;
reverse(s.begin(),s.end());//先全部翻转。
reverse(s.begin(),s.begin()+k);//反转第一个段。
reverse(s.begin()+k,s.end());//翻转第二个段。
cout<<s;
return 0;
}
时间复杂度:O(n) //reverse
空间复杂度:O(1)