字符串
344. 反转字符串
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. 反转字符串 II
class Solution {
public:
string reverseStr(string s, int k) {
for(int i = 0; i < s.size(); i += (2 * k)){
//1.反转k个字符
if(i + k <= s.size()){
reverse(s.begin() + i, s.begin() + i + k);
}
//2.剩余的全部反转
if(i + k > s.size()){
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
这道题目其实也是模拟,实现题目中规定的反转规则就可以了。
一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。
其实在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。
所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
剑指 Offer 05. 替换空格
class Solution {
public:
string replaceSpace(string s) {
//统计更新后的长度,空格数量
int count = 0;
int oldSize = s.size();
for(int i = 0; i < s.size(); i++){
if(s[i] == ' '){
count++;
}
}
//更新长度
s.resize(s.size() + 2 * count);
int newSize = s.size();
//填充
//两个指针同时从后向前遍历
//保留新旧s的长度
for(int i = oldSize - 1, j = newSize - 1; i < j; i--, j--){
if(s[i] != ' '){
s[j] = s[i];
}else{
s[j] = '0';
s[j - 1] = '2';
s[j - 2] = '%';
j -= 2;
}
}
return s;
}
};
数组填充类问题:从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
数组、字符串 差别
这里也给大家拓展一下字符串和数组有什么差别,
字符串是若干字符组成的有限序列,也可以理解为是一个字符数组,但是很多语言对字符串做了特殊的规定,接下来我来说一说C/C++中的字符串。
在C语言中,把一个字符串存入一个数组时,也把结束符 '\0’存入数组,并以此作为该字符串是否结束的标志。
例如这段代码:
char a[5] = "asd";
for (int i = 0; a[i] != '\0'; i++) {
}
1
2
3
在C++中,提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用’\0’来判断是否结束。
例如这段代码:
string a = "asd";
for (int i = 0; i < a.size(); i++) {
}
1
2
3
那么vector< char > 和 string 又有什么区别呢?
其实在基本操作上没有区别,但是 string提供更多的字符串处理的相关接口,例如string 重载了+,而vector却没有。
所以想处理字符串,我们还是会定义一个string类型。
151. 反转字符串中的单词
erase操作的时间复杂度
逻辑很简单,从前向后遍历,遇到空格了就erase。
如果不仔细琢磨一下erase的时间复杂度,还以为以上的代码是O(n)的时间复杂度呢。
想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作。
erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。
那么使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。
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 eraseExtraspace(string &s){
int slow = 0;
//跳过思维,fast遇到非空格处理,即跳过空格
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++;
}
}
}
//通过resize截断
s.resize(slow);
}
string reverseWords(string s) {
eraseExtraspace(s);
reverse(s, 0, s.size() - 1);
//去掉空格后一定从start开始
int start = 0;
//将每个单词反转
for(int i = 0; i <= s.size(); i++){
//i指向空格作为标志
if(i == s.size() || s[i] == ' '){
reverse(s, start, i - 1);
//更新start 反转下一个
start = i + 1;
}
}
return s;
}
};
剑指 Offer 58 - II. 左旋转字符串
思路 如何实现左旋
字符串反转的一个思路:先反转局部,再反转整体
具体步骤为:
- 反转区间为前n的子串
- 反转区间为n到末尾的子串
- 反转整个字符串
最后就可以达到左旋n的目的,而不用定义新的字符串,完全在本串上操作。
例如 :示例1中 输入:字符串abcdefg,n=2
如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oIKWjKJR-1687750774141)(https://code-thinking.cdn.bcebos.com/pics/%E5%89%91%E6%8C%87Offer58-II.%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.png)]
最终得到左旋2个单元的字符串:cdefgab
思路明确之后,那么代码实现就很简单了
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;
}
};