344 反转字符串
思考:
字符串数组的操作,其实就是数组操作,不是很难,原地算法设置一个字符大小的交换空间、同时还是使用头尾双指针来定位将要互换的元素 即可迅速解决。
注意,算法训练阶段,减少库函数的使用,特别是题目要求实现的算法的关键部分能被库函数直接解决的话就避免使用库函数,尝试将算法过程完整复现;但是库函数只涉及解题过程中的一小部分,同时已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
我的代码:
class Solution {
public:
void reverseString(vector<char>& s) {
char mid;
for(int i=0,j=s.size()-1;i<j;i++,j--){
mid=s[i];
s[i]=s[j];
s[j]=mid;
}
}
};
在交换步骤也可以直接使用库函数swap:
class Solution {
public:
void reverseString(vector<char>& s) {
for(int i=0,j=s.size()-1;i<j;i++,j--)
swap(s[i],s[j]);
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
541 反转字符串Ⅱ
思考:
这题的突破口就在:循环迭代第一步要确定每次进行反转的串部分的起点,第二步要确定每次进行反转的串部分的范围(头和尾)。
我的代码:
class Solution {
public:
string reverseStr(string s, int k) {
for(int i=0;;){
int left,right;
//剩余字符小于2k但大于或等于k个,则确定双指针的位置为只反转前k个字符
if((s.size()-i)>=k){
left=i;
right=i+k-1;
}
//剩余字符少于k个,则确定双指针的位置为反转全部剩余字符
else if((s.size()-i)<k){
left=i;
right=s.size()-1;
}
//反转操作
for(;left<right;left++,right--)
swap(s[left],s[right]);
//更新反转部分的起点,若剩余部分字符数量不足2k,则结束循环
if((i+2*k)<s.size()){
i+=2*k;
}else break;
}
return s;
}
};
但是看卡哥的解法说反转的逻辑可以用库函数......alright不过确实反转不是解题关键部分,重点在于确定反转范围的头尾。反转部分使用库函数的写法如下:
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;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
KamaCoder54 替换数字
思考:从后向前使用双指针遍历是一个技巧,这道题是一个典例。
本题是一个数组填充类型的题,同时还是一个双指针的题目。若不想使用额外空间来完成,可以考虑先开辟出将数字替换掉并且填充后的数组需要的大小,然后从新数组尾部向头部填充内容。
具体实现是使用oldSize和newSize两个指针分别指向旧和新数组的尾部,从后向前填充,当旧数组遍历到字母,则将字母存到新数组指针指向的位置;当旧数组遍历到数字,则将number串从后向前一个个字符存到新数组,替换数字字符。直到两指针在头部相遇,循环停止。
从后向前填充的好处在于:
- 不用申请新数组。
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。
其实很多数组填充类的问题,其做法都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
我的代码:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s;
cin>>s;
//事先统计字符串中的数字个数
int numc=0;
//保存扩容之前的数组最后一位的指针
int oldSize=s.size()-1;
for(int i=0;i<s.size();i++)
if(s[i]>='0'&&s[i]<='9')
numc++;
//进行数组扩容
s.resize(s.size()+5*numc);
//保存扩容之后的数组最后一位的指针
int newSize=s.size()-1;
//新旧两数组尾部的指针开始从数组尾部向头部遍历,直到两者在头部相遇
for(;oldSize<newSize;){
//原数组字符为小写字母时,存到新数组对应位置
if(s[oldSize]>='a'&&s[oldSize]<='z')
s[newSize--]=s[oldSize--];
//原数组字符为数字时,新数组开始从后往前填充number串,同时原数组指针向前迭代
if(s[oldSize]>='0'&&s[oldSize]<='9'){
s[newSize--]='r';
s[newSize--]='e';
s[newSize--]='b';
s[newSize--]='m';
s[newSize--]='u';
s[newSize--]='n';
oldSize--;
}
}
cout<<s<<endl;
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
*文章是本人刷题过程中的一些笔记和理解,记录的解析不一定足够清晰,也可能存在本人暂未意识到的错误,如有问题欢迎大家指出。文章中学习到的解法来自代码随想录的B站视频(字符串1~2)以及代码随想录的学习网站。