参考:代码随想录
1.1 反转字符串
链接:344.反转字符串
题目描述:
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
方法:
双指针法,我们定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
代码:
class Solution {
public:
void reverseString(vector<char>& s) {
// 1.库函数:
//reverse(s.begin(), s.end());
// 2.双指针交换:
for (int i = 0, j = s.size() - 1; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
};
1.2 反转字符串II
链接: 541. 反转字符串II
题目描述:
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
- 如果剩余字符少于 k 个,则将剩余字符全部反转。
- 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
示例 1:
输入:s = “abcdefg”, k = 2
输出:"bacdfeg"s 的形式给出。
方法:
在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
不用一个一个字符计数判断。
代码:
我的:没有用库函数
class Solution {
public:
// 将下标为i-j之间的字符反转
void reverse(string &s, int i, int j) {
while (i < j) {
swap(s[i], s[j]);
i++;
j--;
}
}
string reverseStr(string s, int k) {
int cnt = 0; // 计数
int i = 0;
while (i < s.length()) {
if (s.length() - i < k) {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
reverse(s, i, s.length() - 1);
} else {
// 反转前 k 个字符,其余字符保持原样。
reverse(s, i, i + k - 1);
}
i += 2 * k;
}
return s;
}
};
那么这里具体反转的逻辑我们要不要使用库函数呢,其实用不用都可以,使用reverse来实现反转也没毛病,毕竟不是解题关键部分。
使用C++库函数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;
}
};
1.3 替换数字
链接: 替换数字
题目描述:
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。
示例 1:
对于输入字符串 “a1b2c3”,函数应该将其转换为 “anumberbnumbercnumber”。
我的 方法一:另外开辟空间存放结果
使用新的string来存放结果,遍历字符串中每一个字符,遇到数字,就在结果末尾加上number,否则在结果末尾加上这个字符。
代码:
string numberToString(string s) {
string str;// 存放结果
for (int i = 0; i < s.length(); i++) {
if (s[i] >= '0' && s[i] <= '9') {
str += "number";
} else {
str += s[i];
}
}
return str;
}
方法二:数组扩容,并从后往前填充数据
很多数组填充类的问题,其做法都是先预先给数组扩容到填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
- 不用申请新数组,string扩充函数是 s.resize(xxx);
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
注意:这里的新数组指针只用移动到旧指针相同的位置就行了,不用一直往前移动直到数组的开头。
代码:
#include<iostream>
using namespace std;
int main() {
string s;
while (cin >> s) {
int count = 0; // 统计数字的个数
int sOldSize = s.size();
for (int i = 0; i < s.size(); i++) {
if (s[i] >= '0' && s[i] <= '9') {
count++;
}
}
// 扩充字符串s的大小,也就是每个空格替换成"number"之后的大小
s.resize(s.size() + count * 5);
int sNewSize = s.size();
// 从后先前将空格替换为"number"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
// j < i是因为这里的新数组指针只用移动到旧指针相同的位置就行了,不用一直往前移动直到数组的开头。
if (s[j] > '9' || s[j] < '0') {
s[i] = s[j];
} else {
s[i] = 'r';
s[i - 1] = 'e';
s[i - 2] = 'b';
s[i - 3] = 'm';
s[i - 4] = 'u';
s[i - 5] = 'n';
i -= 5; // 之后在for循环里面还有一个i--
}
}
cout << s << endl;
}
}
1.4 翻转字符串里的单词
链接: 151.翻转字符串里的单词
题目描述:
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
输入:s = “the sky is blue”
输出:“blue is sky the”
方法一:使用库函数
很多语言对字符串提供了 split(拆分),reverse(翻转)和 join(连接)等方法,因此我们可以简单的调用内置的 API 完成操作。
例如在Java中:
- 使用 split 将字符串按空格分割成字符串数组;
- 使用 reverse 将字符串数组进行反转;
- 使用 join 方法将字符串数组拼成一个字符串。
Java代码:
class Solution {
public String reverseWords(String s) {
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List<String> wordList = Arrays.asList(s.split("\\s+"));
Collections.reverse(wordList);
return String.join(" ", wordList);
}
}
方法二:C++两次反转
我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。
使用整体反转+局部反转就可以实现反转单词顺序的目的。
- 移除多余空格:
- 将整个字符串反转
- 将每个单词反转
举个例子,源字符串为:"the sky is blue "
- 移除多余空格 : “the sky is blue”
- 字符串反转:“eulb si yks eht”
- 单词反转:“blue is sky the”
移除多余空格:
方法一:使用辅助字符串,空间复杂度为O(n)
申请一个新的字符串,遇到多余的空格就跳过,把其余的字符放到新的字符串里面,这样就实现了移除多余的空格这种操作;
方法二:快慢指针,空间复杂度为O(1)
slow指向下一个要填入的位置,fast指向当前正在遍历的字符,在原来已有的字符串上进行操作。
因为中间每个单词之间都要加上一个空格,所以不用判断原来的字符串中每个单词之间有几个空格,只留下一个空格,而是通过在每个单词之间加空格的方法来处理。
把每个单词看做是一个整体,如果当前单词是第一个单词,那么前面就不需要加空格,如果不是第一个单词,那么每个单词前面就要加上空格。
代码:
class Solution {
public:
// 将下标为i-j之间的字符反转
void reverse(string &s, int i, int j) {
while (i < j) {
swap(s[i], s[j]);
i++;
j--;
}
}
string reverseWords(string s) {
// 去掉多余的空格
eraseSpace(s);
if (s.length() == 0) {
return s;
}
// 翻转整个字符串
reverse(s, 0, s.length() - 1);
// 翻转单个单词
int start = 0; // 每个单词的开始位置
for (int i = 0; i < s.length(); i++) {
if (s[i] == ' ') {
reverse(s, start, i - 1);
start = i + 1;
}
}
// 翻转最后一个单词
reverse(s, start, s.length() - 1);
return s;
}
};
其中 ,去掉多余的空格,代码如下:
// 方法一:比较繁琐
void eraseSpace(string &s) {
// 用快慢指针
int slow = 0, fast = 0; // slow指向下一个要填入的位置,fast指向当前正在遍历的字符
// 1.删除开头的空格,找到第一个不为空的字符
while (fast < s.length() && s[fast] == ' ') {
fast++;
}
if (fast == s.length()) {
s = "";
}
// 2. 删除中间多余的空格
while (fast < s.length()) {
while (fast < s.length() && s[fast] != ' ') {
s[slow++] = s[fast++];
}
// 找到下一个不为空的字符
while (fast < s.length() && s[fast] == ' ') {
fast++;
}
// 如果后面还有不为空的单词,中间两个单词之间要用空格来分隔开
if (fast < s.length()) {
s[slow++] = ' ';
}
// 如果fast = s.length(),说明走到了末尾,
}
// 3.修改字符串大小,相当于删除掉末尾的空格
s.resize(slow);
}
// 方法二:精简版
void eraseSpace(string &s) {
// 用快慢指针
int slow = 0; // slow指向下一个要填入的位置,fast指向当前正在遍历的字符
for (int fast = 0; fast < s.length(); fast++) {
// 遇到非空格,就把整个单词都移过去
if (s[fast] != ' ') {
// 如果不是第一个单词,那么单词开头要加空格
if (slow != 0) {
s[slow++] = ' ';
}
// 把整个单词都移过去
while (fast < s.length() && s[fast] != ' ') {
s[slow++] = s[fast++];
}
}
}
// 3.修改字符串大小,相当于删除掉末尾的空格
s.resize(slow);
}
1.5 右旋转字符串
链接: 右旋转字符串
题目描述:
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
示例 :
例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。
方法:
使用整体反转+局部反转:先整体反转1次,再分别把两个子字符串进行反转。
代码:
#include <iostream>
using namespace std;
// 将下标为i-j之间的字符反转
void reverse(string &s, int i, int j) {
while (i < j) {
swap(s[i], s[j]);
i++;
j--;
}
}
void fun(string &s, int k) {
reverse(s, 0, s.length() - 1);
reverse(s, 0, k - 1);
reverse(s, k, s.length() - 1);
}
int main() {
int k;
cin >> k;
string s;
cin >> s;
fun(s, k);
cout << s << endl;
return 0;
}