344.反转字符串
本题是字符串基础题目,就是考察 reverse 函数的实现,同时也明确一下 平时刷题什么时候用 库函数,什么时候 不用库函数
题目:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、
思路
先说一说题外话:
对于这道题目一些同学直接用C++里的一个库函数 reverse,调一下直接完事了, 相信每一门编程语言都有这样的库函数。
如果这么做题的话,这样大家不会清楚反转字符串的实现原理了。
但是也不是说库函数就不能用,是要分场景的。
如果在现场面试中,我们什么时候使用库函数,什么时候不要用库函数呢?
如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。
如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数
在反转链表中,使用了双指针的方法。
那么反转字符串依然是使用双指针的方法,只不过对于字符串的反转,其实要比链表简单一些。
因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。
对于字符串,我们定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
以字符串hello
为例,过程如下:
https://code-thinking.cdn.bcebos.com/gifs/344.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.gif
在字符串相关的题目中,库函数对大家的诱惑力是非常大的,因为会有各种反转,切割取词之类的操作,这也是为什么字符串的库函数这么丰富的原因。
C代码如下:
void reverseString(char* s, int sSize) {
int left=0,right=sSize-1;
while(left<right){
char temp=s[left];
s[left]=s[right];
s[right]=temp;
left++;
right--;
}
}
541.反转字符串II
题目: 给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样
思路
此题题目说的很绕,就是 2k
一组,翻转 k
个,如果不够 k
,全部翻转该组
C++代码如下:
class Solution {
public:
string reverseStr(string s, int k) {
int n=s.size(),pos=0;
while(pos<n){
if(pos+k<n){
reverse(s.begin()+pos,s.begin()+pos+k);//剩余字符串个数大于等于K的情况
}
else{
reverse(s.begin()+pos,s.end());//剩余字符串不足K的情况
}
pos+=2*k;
}
return s;
}
};
卡码网:54.替换数字
题目: 给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。
思路
- 暴力解法
#include<iostream>
#include<string>
using namespace std;
int main(void){
string s;
cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]>='0' && s[i]<='9'){
s.replace(i,1,"number");
i+=5;
}
}
cout<<s<<endl;
return 0;
}
- 双指针法
首先扩充数组到每个数字字符替换成 "number" 之后的大小。
例如 字符串 "a5b" 的长度为3,那么 将 数字字符变成字符串 "number" 之后的字符串为 "anumberb" 长度为 8。
然后从后向前替换数字字符,也就是双指针法,过程如下:i指向新长度的末尾,j指向旧长度的末尾。如图:
https://code-thinking-1253855093.file.myqcloud.com/pics/20231030173058.png
有同学问了,为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。
其实很多数组填充类的问题,其做法都是先预先给数组扩容至填充后的大小,然后再从后向前进行操作。
这么做有两个好处:
- 不用申请新数组。
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
C++代码如下:
此时算上本题,我们已经做了七道双指针相关的题目了分别是:
151.翻转字符串里的单词
这道题目基本把 刚刚做过的字符串操作 都覆盖了,不过就算知道解题思路,本题代码并不容易写,要多练一练。
题目: 给你一个字符串 s
,请你反转字符串中 单词 的顺序。
思路
这道题目可以说是综合考察了字符串的多种操作
一些同学会使用split库函数,分隔单词,然后定义一个新的string字符串,最后再把单词倒序相加,那么这道题题目就是一道水题了,失去了它的意义。
想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。
所以解题思路如下:
- 移除多余空格
- 将整个字符串反转
- 将每个单词反转
举个例子,源字符串为:"the sky is blue "
- 移除多余空格 : "the sky is blue"
- 字符串反转:"eulb si yks eht"
- 单词反转:"blue is sky the"
这样我们就完成了翻转字符串里的单词。
使用双指针法来去移除空格
这部分和27.移除元素的逻辑是一样的,本题是移除空格,而27.移除元素就是移除元素。
所以代码可以写的很精简,大家可以看如下代码 removeExtraSpaces 函数的实现:
此时我们已经实现了removeExtraSpaces函数来移除冗余空格。
还要实现反转字符串的功能,支持反转字符串子区间
代码如下
完整代码如下:
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;
for(int fast=0;fast<s.size();fast++){
if(s[fast] != ' '){//遇到非空格就处理,即删除所有空格
if(slow != 0)s[slow++]=' ';//给单词之间添加空格 slow != 0说明不是第一个单词
while(fast<s.size() && s[fast]!=' '){
s[slow++]=s[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++){
if(s[i] == ' ' || i==s.size()){//遇到空格或到达串尾,说明一个单词结束,进行反转
reverse(s,start,i-1);//单词反转
start=i+1;//更新下一个单词的开始下标
}
}
return s;
}
};
难点:难点在如何移除多余空格和反转单词时for循环的循环条件如何设置?
卡码网 55.右旋字符串
题目: 字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
思路
本题中,我们需要将字符串右移n位,字符串相当于分成了两个部分,如果n为2,符串相当于分成了两个部分,如图:(length为字符串长度)
右移n位, 就是将第二段放在前面,第一段放在后面,先不考虑里面字符的顺序,是不是整体反转不就行了。如图:
此时第一段和第二段的顺序是我们想要的,但里面的字符位置被我们倒叙,那么此时我们再把第一段和第二段里面的字符再倒叙一把,这样字符顺序不就正确了。
其实,思路就是通过整体倒叙,把两段子串顺序颠倒,两段子串里的的字符再倒叙一把,负负得正,这样就不影响子串里面字符的顺序了。
C++代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
int main(void){
int k;
string s;
cin >> k;
cin >> s;
reverse(s.begin(),s.end());//整体反转
reverse(s.begin(),s.begin()+k);//先反转前一段,长度k
reverse(s.begin()+k,s.end());//再反转后一段,长度len-k
cout << s <<endl;
}
写在最后:今天最难的一题就是反转字符串里的单词,思路虽然简单,但其实代码里有非常多细节需要注意,代码理解起来也不轻松,代入一个简单的例子跟着代码走一遍去理解是最有效的。