方法一:双指针
思路和算法
我们也可以不使用语言中的 API,而是自己编写对应的函数。在不同语言中,这些函数实现是不一样的,主要的差别是有些语言的字符串不可变(如 Java 和 Python),有些语言的字符串可变(如 C++)。
对于字符串不可变的语言,首先得把字符串转化成其他可变的数据结构,同时还需要在转化的过程中去除空格。
对于字符串可变的语言,就不需要再额外开辟空间了,直接在字符串上原地实现。在这种情况下,反转字符和去除空格可以一起完成。
代码如下:
class Solution {
public:
// 版本一:覆盖法消除空格
void eraseSpaces(string &s) {
int n = s.size();
int slowIndex = 0;
int fastIndex = 0;
// 去除前导空格
while (fastIndex < n and s[fastIndex] == ' ') {
fastIndex ++;
}
// 去除单词间的多个空格
for (; fastIndex < n; fastIndex ++) {
if (fastIndex > 1 // 此时快指针指向一个字母,大于1是因为s中至少存在一个单词
and s[fastIndex] == ' '
and s[fastIndex] == s[fastIndex - 1]) {
continue;
}
s[slowIndex++] = s[fastIndex];
}
// 去除尾随空格
if (slowIndex > 1 and s[slowIndex - 1] == ' ') {
s.resize(slowIndex - 1);
} else {
s.resize(slowIndex);
}
}
string reverseWords(string s) {
eraseSpaces(s); // 去除多余的空格
reverse(s.begin(), s.end()); // 反转整个字符串
// [start, end)前闭后开区间
int start = 0; // 反转字符串中单词的开头位置
int end = 0; // 反转字符串中单词的结束位置
bool entry = false; // 标记枚举反转字符串的过程中是否进入单词区间
int n = s.size();
for (int i = 0; i < n; i++) {
// 确定单词的开头位置
if ((!entry) or (s[i - 1] == ' ' and s[i] != ' ')) {
start = i;
entry = true;
}
// 确定单词的结束位置
if (entry and s[i] == ' ' and s[i - 1] != ' ') {
end = i;
entry = false;
reverse(s.begin() + start, s.begin() + end);
}
// 最后一个结尾单词之后后
if (entry and (i == n -1) and s[i] != ' ') {
entry = false;
reverse(s.begin() + start, s.end());
}
}
return s;
}
// 当然这里的主函数reverseWords写的有一些冗余的,可以精简一些,精简之后的主函数为:
/* 主函数简单写法
string reverseWords(string s) {
eraseSpaces(s);
reverse(s.begin(), s.end());
for(int i = 0; i < s.size(); i++) {
int j = i;
// 查找单词间的空格,翻转单词
while(j < s.size() && s[j] != ' ') j++;
reverse(s.begin() + i, s.begin() + j);
i = j;
}
return s;
}
*/
};
或者,通过增加一个字符串s2的空间,也可实现去除空格的函数,如下所示:
// 版本二
void eraseSpaces(string &s) {
int left = 0;
int right = s.size() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s[left] == ' ') {
++left;
}
// 去掉字符串末尾的空白字符
while (left <= right && s[right] == ' ') {
--right;
}
// 将字符串间多余的空白字符去除
string s2 = "";
while (left <= right) {
char c = s[left];
if (c != ' ') {
s2 += c;
} else if (s2[(int)s2.size() - 1] != ' ') {
s2 += c;
}
left ++;
}
s = s2;
}
去除空格的函数可以进一步简化,如下所示:
// 版本三
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0; //整体思想参考https://programmercarl.com/0027.移除元素.html
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的大小即为去除多余空格后的大小。
}
方法二:双端队列
思路和算法
由于双端队列支持从队列头部插入的方法,因此我们可以沿着字符串一个一个单词处理,然后将单词压入队列的头部,再将队列转成字符串即可。
class Solution {
public:
string reverseWords(string s) {
int left = 0, right = s.size() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s[left] == ' ') ++left;
// 去掉字符串末尾的空白字符
while (left <= right && s[right] == ' ') --right;
deque<string> d;
string word;
while (left <= right) {
char c = s[left];
if (word.size() && c == ' ') {
// 将单词 push 到队列的头部
d.push_front(move(word));
word = "";
}
else if (c != ' ') {
word += c;
}
++left;
}
// 最后一个单词后的空格已被去掉,所以需要手动添加
d.push_front(move(word));
string ans; // 默认为""
while (!d.empty()) {
ans += d.front();
d.pop_front();
if (!d.empty()) ans += ' ';
}
return ans;
}
};
参考资料
https://leetcode.cn/problems/reverse-words-in-a-string/solution/fan-zhuan-zi-fu-chuan-li-de-dan-ci-by-leetcode-sol/
https://leetcode.cn/problems/reverse-words-in-a-string/solution/dai-ma-sui-xiang-lu-dai-ni-gao-ding-zi-f-hg23/