LeetCode 151. 反转字符串中的单词 解析
这个问题要求将给定字符串中的单词顺序反转,同时移除多余空格,使得每个单词之间只有一个空格,且字符串首尾无空格。
方法思路
最优解法是原地反转法:通过三次反转操作实现单词顺序的反转,同时处理多余空格。具体步骤如下:
-
移除多余空格 :
将字符串中的多余空格去除,只保留单词间的单个空格,并调整字符串长度。
-
整体反转 :
将处理后的字符串整体反转。
-
单词反转 :
遍历字符串,对每个单词进行局部反转,恢复单词的原始顺序。
C++ 代码实现
cpp
#include <string>
#include <algorithm>
class Solution {
public:
string reverseWords(string s) {
// 1. 移除多余空格
int i = 0, j = 0; // i 为写入指针,j 为读取指针
while (j < s.length()) {
// 跳过前导空格
while (j < s.length() && s[j] == ' ') j++;
if (j >= s.length()) break; // 处理全空格的情况
// 添加单词和中间空格
if (i != 0) s[i++] = ' '; // 单词间添加一个空格
while (j < s.length() && s[j] != ' ') {
s[i++] = s[j++];
}
}
s.resize(i); // 调整字符串长度
// 2. 整体反转
reverse(s.begin(), s.end());
// 3. 逐个单词反转
i = 0;
for (int j = 0; j <= s.length(); j++) {
if (j == s.length() || s[j] == ' ') { // 遇到空格或字符串末尾
reverse(s.begin() + i, s.begin() + j);
i = j + 1; // 更新下一个单词的起始位置
}
}
return s;
}
};
代码解释
-
移除多余空格:
- 使用双指针
i
和j
,其中j
用于遍历原字符串,i
用于写入处理后的字符。 - 跳过前导空格,遇到单词时将其复制到
i
位置,并在单词间添加单个空格。 - 最终使用
resize(i)
调整字符串长度,去除尾部冗余字符。
- 使用双指针
-
整体反转 :
使用
reverse(s.begin(), s.end())
将整个字符串反转,此时单词顺序已反转,但每个单词内部字符顺序也被反转。 -
单词反转:
- 遍历字符串,每当遇到空格或字符串末尾时,将当前单词(从
i
到j-1
)进行局部反转。 - 更新
i
为下一个单词的起始位置(j+1
)。
- 遍历字符串,每当遇到空格或字符串末尾时,将当前单词(从
复杂度分析
- 时间复杂度:O(n),其中 n 是字符串的长度。每个字符最多被处理三次(移除空格、整体反转、单词反转)。
- 空间复杂度:O(1),仅需常数级的额外空间。
示例
输入:s = " hello world! "
输出:"world! hello"
解释:
- 移除多余空格 :
"hello world!"
- 整体反转 :
"!dlrow olleh"
- 单词反转 :
"world! hello"
关键点
-
原地修改 :
通过双指针法在原字符串上直接操作,避免额外空间开销。
-
三次反转策略 :
整体反转+单词反转的组合巧妙地实现了单词顺序的反转,同时保持单词内容正确。
-
边界处理:
- 处理字符串首尾的多余空格。
- 处理连续多个空格的情况。
- 处理空字符串或全空格字符串的情况。
这种方法高效且符合题目要求,是解决此类问题的经典思路。
方法二:
cpp
class Solution {
public:
string reverseWords(string s) {
stringstream ss(s);
vector<string> words;
string word;
while (ss >> word)
{
words.emplace_back(word);
}
string res = words[words.size() - 1];
for (int i = words.size() - 2; i >= 0; --i)
{
res += " ";
res += words[i];
}
return res;
}
};