字符串简介
首先还是来简单介绍一下字符串,首先区别一下c++中char *、char []、string
的区别:
char *
是一个指向字符串的指针,可以直接使用字符串赋值,如char *s = "abc"
。char []
本质上是一个数组,变量名指向数组的第一个元素,如char str[5]
。另外注意字符型数组最后一个元素会存放一个\0
。例如char str[5] = {"abcde"};
是不合法的,因为末尾必须有一个\0
。string
是c++中的一个类,拥有自己封装好的类成员函数。
下面复习一下string类的一些常用函数:
//初始化
//string也是类,可以像vector以及其他的c++容器类似的方式初始化
string s1("abc");
string s2(s1);
string s3(10,'a'); //初始化10个a
string s4 = "tuboshu"; //c风格的初始化
//获取长度
s1.size();
s1.length(); //两者本质上相同
//添加
s1.append(s2); //go中也是这样拼接的
s1.push_back('a');
s1.insert(...); //insert函数重载有很多但是不常用到
//查找
int index = s4.find("bo"); //返回一个index是"bo"字符穿首次出现的首字母的位置
//删除
s4.erase(s4.begin(),s4.begin() + 2); //删除指定位置
s4.erase(2); //删除从下标2到结束的字符串
s4.erase(2,2); //删除从下标2开始长度为2的字符串
//substr获取字串 substr(pos,len) pos的下标从0开始
string s5 = s4.substr(1); //获取s4的第2个元素到结束构成字串
string s6 = s4.substr(2,1); //从s4的第2个元素长度为1的字串
string s7 = s4.substr(s4.find("b"));
另外,因为重载了+
运算符,所以string类型的拼接可以直接使用运算符操作。
参考文章:C++string类常用方法。
344.反转字符串
两种方法:1.直接调用reverse()
函数
2.双指针法,两个指针分别从头尾向内收缩完成交换:
void reverseString(vector<char>& s) {
int left = 0;
int right = s.size()-1;
while(left<right){
swap(s[left++],s[right--]);
}
}
本题没什么好说的,可以对比一下206. 反转链表。
541. 反转字符串 II
本题翻转的逻辑和上一题一模一样,只不过在遍历字符串时需要注意一些细节,也就是模拟的过程。另外需要处理一下遍历到末尾时的特殊情况。
代码如下:
string reverseStr(string s, int k) {
for(int i = 0; i < s.size(); ){
//分为两种情况,一种是剩下的字符小于k,另一种是大于等于k
if(s.size() - i >= k){
int left = i;
int right = i + k - 1;
while(left < right){
swap(s[left++],s[right--]);
}
//模拟过程的时候i每次增加2k
i += 2 * k;
}else{
int left = i;
int right = s.size() - 1;
while(left < right){
swap(s[left++],s[right--]);
}
break;
}
}
return s;
}
翻转子字符串的逻辑这里可以直接使用reverse()
函数代替更简洁。
剑指 Offer 05. 替换空格
很明显本题输出字符串的size
和输入的不同,所以有一个resize()
的操作。卡哥说不申请额外空间完成,这种类似移除元素一般用双指针法,但是本题是从后往前遍历,这样就可以避免从前往后遍历时的时间复杂度为O(n^2)。
先找出空格的个数,然后重新分配字符串长度,这里应该是原来的size加2*空格数量,因为空格本身也占用一个位置。。然后就是替换的逻辑。
代码如下:
class Solution {
public:
string replaceSpace(string s) {
int count = 0;
int size = s.size();
for(int i = 0; i < s.size(); i++){
if(s[i] == ' ') count++;
}
if(count == 0) return s;
s.resize(size + count * 2);
int left = size - 1;
int right = s.size() - 1;
while(left >= 0){
if(s[left] != ' '){
s[right--] = s[left--];
continue;
}
s[right--] = '0';
s[right--] = '2';
s[right--] = '%';
left--;
}
return s;
}
};
151. 反转字符串中的单词
本题的确很烦人,代码看起来很简洁,实际上背后隐藏了很多逻辑和情况。
大体思路首先应该去掉多余空格,包括头尾和中间的多余空格,然后再翻转,翻转的逻辑使用先整体再部分,也是很巧妙的一种思路。
仔细想一下可以发现去除多余空格可以将所有空格全部删除,并在两个单词之间补上需要的空格,这样就不需要分开讨论了,就变成了删除字符串元素的题,再做一些特殊处理,代码如下:
int slow = 0 , fast = 0;
//去空格
for(;fast < s.size(); fast++){
if(s[fast] != ' '){
if(slow != 0) s[slow++] = ' ';
while(fast < s.size() && s[fast] != ' '){
s[slow] = s[fast];
slow++;
fast++;
}
}
}
使用双指针法,快指针指向主要用来遍历数组,慢指针指向需要更新当前元素的位置。这里为什么要在s[fast] != ' '
里加一个while循环呢,因为进入条件后首先需要判断的是是否需要补空格,在一个完整的单词后补空格是需要移动慢指针的,所以当s[fast] != ' '
也就是遍历遇到需要保留下来的元素时,应该把一个完整的单词处理完再进入下一次循环。或者反过来想一下,如果这样写:
for(;fast < s.size(); fast++){
if(s[fast] != ' '){
if(slow != 0) s[slow++] = ' ';
s[slow] = s[fast];
slow++;
fast++;
}
}
}
这就是一个普通的移除元素的逻辑,这样会导致每次遇到一个字母都会在后面加一个空格,显然是不对的。在while循环中加入fast < s.size()
判断是防止遍历到最后一个元素时fast++
导致内存读取错误。
接下来就是部分翻转的逻辑:
//翻转
int pos = 0;
for(int i = 0; i < s.size(); i++){
if(s[i] == ' '){
reverse(s.begin() + pos, s.begin() + i);
pos = i + 1;
}
if(i == s.size() - 1) reverse(s.begin() + pos, s.end());
}
其实一开始我是这样写的:
//翻转
int pos = 0;
for(int i = 0; i < s.size(); i++){
if(s[i] == ' '){
reverse(s.begin() + pos, s.begin() + i);
pos = i + 1;
}
}
这样会导致最后一个单词不能正常翻转,因为reverse()
函数是左闭右开的,所以另外加一个特殊处理,或者像卡哥一样自己写一个左闭右闭的reverse函数。
最后是完整的代码:
class Solution {
public:
string reverseWords(string s) {
int slow = 0 , fast = 0;
//去空格
for(;fast < s.size(); fast++){
if(s[fast] != ' '){
if(slow != 0) s[slow++] = ' ';
while(fast < s.size() && s[fast] != ' '){
s[slow] = s[fast];
slow++;
fast++;
}
}
}
s.resize(slow);
reverse(s.begin(), s.end());
//翻转
int pos = 0;
for(int i = 0; i < s.size(); i++){
if(s[i] == ' '){
reverse(s.begin() + pos, s.begin() + i);
pos = i + 1;
}
if(i == s.size() - 1) reverse(s.begin() + pos, s.end());
}
return s;
}
};
总结一下,双指针法的思想固然重要,但是想要能一次写出AC出来的代码,对细节和多种情况的把握才是关键。
剑指Offer58-II.左旋转字符串
在空间复杂度为O(1)的情况下,使用整体+部分翻转达成左旋效果,还是比较简单的。
代码如下:
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;
}
还是要注意reverse()
在翻转字符串时的效果。