<think>我们被要求编写一个C++程序,反转字符串中的单词顺序并移除多余空格。
例如:输入字符串 " hello world " 应该输出 "world hello"
步骤:
1. 移除多余空格:将字符串中的空格规范化,即单词间只保留一个空格,并移除开头和结尾的空格。
2. 反转整个字符串:这样单词的顺序就反了,但每个单词内部的字母顺序也反了。
3. 反转每个单词:将每个单词的字母顺序再反转回来。
另一种方法(可能更直观):
1. 先移除多余空格(可以边处理边移除,或者先处理空格)。
2. 反转整个字符串。
3. 然后反转每个单词。
或者:
1. 使用双指针移除多余空格(包括开头、中间和结尾)。
2. 反转整个字符串。
3. 反转每个单词。
详细步骤:
步骤1:移除多余空格
我们可以遍历字符串,使用双指针技巧,快指针和慢指针。
同时,我们还要处理单词之间空格数量(只保留一个)和开头结尾无空格。
步骤2:反转整个字符串
使用双指针从两端向中间交换字符。
步骤3:反转每个单词
遍历字符串,当遇到空格或到达末尾时,反转从单词开头到当前空格前一个位置的子串。
但是,我们也可以先反转整个字符串,然后移除空格?这样可能不好处理,因为空格位置会影响单词边界。
因此,我们按以下顺序:
1. 移除多余空格(同时可以去掉首尾空格,并保证单词间只有一个空格)。
2. 反转整个字符串。
3. 反转每个单词。
或者,我们也可以:
1. 移除多余空格(得到规范字符串)。
2. 反转每个单词(这样每个单词都反了,但单词顺序没变)。
3. 反转整个字符串(得到单词顺序反转,且每个单词的字母顺序正确)。
但是,如果我们先反转每个单词,再反转整个字符串,那么结果就是单词顺序反转,但每个单词的字母顺序正确吗?
例如:原字符串 "hello world" -> 先反转每个单词: "olleh dlrow" -> 再反转整个字符串: "world hello"
所以步骤可以是:
1. 移除多余空格(得到"hello world")。
2. 反转每个单词(得到"olleh dlrow")。
3. 反转整个字符串(得到"world hello")。
两种方法都可以,我们选择第二种方法:先移除多余空格,然后反转每个单词,最后反转整个字符串。
具体实现:
移除多余空格函数(使用双指针):
初始化两个指针:慢指针slow指向0,快指针fast从0开始遍历。
我们还需要一个变量记录上一个字符是否是空格(用于跳过连续空格)。
同时,我们还要处理开头和结尾的空格。但双指针可以这样处理:
- 跳过开头的空格:如果开头有空格,我们让fast先走,直到遇到非空格。
- 然后,在循环中,如果遇到空格,我们只保留一个空格(但要注意,在单词之间我们保留一个空格,而结尾不能有空格)。
具体步骤(移除多余空格):
1. 跳过开头空格:让fast指针先移动到第一个非空格字符。
2. 然后,开始循环,当fast小于字符串长度时:
如果当前字符不是空格,直接复制到slow位置,slow++,fast++。
如果当前字符是空格,那么我们需要检查下一个字符是否也是空格(连续空格),但我们只保留一个空格,所以先记录这个空格(复制到slow,然后slow++,fast++),然后跳过后面连续的空格(直到遇到非空格)。
但是这样可能会在结尾留下一个空格,所以最后需要处理结尾空格。
另一种更简单的方法(移除多余空格):
我们可以先移除开头的空格,然后处理中间,最后移除结尾空格。但这样可能需要多次遍历。
我们使用双指针一次遍历完成:
1. 设置slow=0, fast=0。
2. 循环:fast指针遍历整个字符串。
- 如果当前字符不是空格,那么直接复制到slow,slow++,fast++。
- 如果当前字符是空格,那么检查:如果这个空格是单词后的第一个空格(即上一个字符是非空格),那么我们保留它(复制到slow,slow++,fast++),然后跳过后面连续的空格(fast继续前进直到非空格或结尾)。如果上一个字符已经是空格,那么我们就跳过当前空格(不复制,只移动fast)。
3. 循环结束后,我们可能最后多了一个空格(如果原字符串结尾有空格,我们只保留了一个,但单词后面不应该有空格),所以检查slow指针最后一位如果是空格,那么将slow减1(即去掉这个空格)。
但是,在循环中,我们也可以避免在结尾添加空格:我们在循环中确保只在单词之间添加空格(即当遇到空格且后面还有非空格单词时才添加空格)。这需要我们在添加空格时判断后面是否还有单词。
实际上,我们可以这样:
在循环中,当遇到空格时,我们不立即添加,而是标记需要添加一个空格(但需要等到下一个单词出现时再添加)。这样我们可以避免结尾的空格。
但是,为了简单,我们可以先移除多余空格(包括中间连续空格)并保留一个空格分隔单词,然后检查最后是否有空格,如果有就删除。
具体实现(移除多余空格):
我们使用一个while循环跳过开头空格(fast指针移动到第一个非空格)。
然后,当fast小于n时:
while (fast < n && s[fast] != ' ') { // 复制一个单词
s[slow++] = s[fast++];
}
// 现在fast指向空格,跳过所有连续空格
while (fast < n && s[fast] == ' ') {
fast++;
}
// 如果后面还有非空格字符(即fast没有到结尾),说明当前单词后面还有单词,那么我们在当前单词后面添加一个空格
if (fast < n) {
s[slow++] = ' '; // 添加一个空格分隔
}
这样,最后就不会有结尾空格,因为只有当后面还有单词时才添加空格。
步骤:
1. 移除多余空格(使用上述方法)。
2. 反转每个单词:在移除空格后的字符串中,我们遍历字符串,对于每个单词,使用双指针反转(从单词开头到结尾)。
3. 反转整个字符串。
注意:由于我们移除了多余空格,字符串长度发生了变化,所以需要调整字符串大小。
我们写代码时,可以这样:
我们直接修改传入的字符串(假设我们可以修改,且使用std::string)。
代码结构:
void reverseWords(string &s) {
// 步骤1:移除多余空格(使用双指针)
// 步骤2:反转每个单词
// 步骤3:反转整个字符串
}
但是,我们也可以使用标准库函数,但题目要求自己实现,所以我们自己写反转函数。
示例:
输入:s = " hello world "
移除多余空格后:s = "hello world"(注意,字符串长度变为11)
反转每个单词:"olleh dlrow"
反转整个字符串:"world hello"
反转函数(反转一个子串,从left到right):
我们可以写一个辅助函数:reverseSubstr(string &s, int left, int right)
注意:在反转每个单词时,我们需要找到每个单词的起始和结束位置。
详细步骤:
步骤1:移除多余空格
int n = s.size();
int slow = 0, fast = 0;
// 跳过开头空格
while (fast < n && s[fast]==' ') fast++;
while (fast < n) {
// 复制一个单词
while (fast < n && s[fast]!=' ') {
s[slow] = s[fast];
slow++;
fast++;
}
// 跳过空格
while (fast < n && s[fast]==' ') fast++;
// 如果后面还有单词,添加一个空格
if (fast < n) {
s[slow] = ' ';
slow++;
}
}
// 现在slow就是新字符串的长度
s.resize(slow);
步骤2:反转每个单词
int start = 0;
for (int i = 0; i <= slow; i++) {
// 当遇到空格或字符串结尾时,反转上一个单词
if (i == slow || s[i]==' ') {
// 反转从start到i-1
reverseSubstr(s, start, i-1);
start = i+1; // 下一个单词的起始位置
}
}
步骤3:反转整个字符串
reverseSubstr(s, 0, slow-1);
辅助函数reverseSubstr:
void reverseSubstr(string &s, int left, int right) {
while (left < right) {
swap(s[left], s[right]);
left++;
right--;
}
}
但是注意:步骤2中,我们反转每个单词时,字符串长度已经是slow了,所以循环条件i<=slow(因为当i==slow时,我们要反转最后一个单词,因为最后一个单词后面没有空格)。
测试例子:
输入:s = "hello world"(移除空格后)
步骤2:反转每个单词:
i=0~5: 当i=5时,s[5]==' ',反转[0,4] -> "olleh world"
start=6,然后i=10时(slow=11,所以i<11,继续)直到i=11(等于slow),反转[6,10] -> "dlrow"
然后整个字符串变为 "olleh dlrow"
步骤3:反转整个字符串:从0到10,得到 "world hello"
注意:上面的例子中,移除空格后的字符串为"hello world",长度为11(索引0~10)。
但是,我们还需要考虑边界情况,比如空字符串,或者只有一个单词。
考虑只有一个单词的情况:例如输入 "hello"
移除多余空格后:s="hello"(长度为5)
步骤2:反转每个单词,当i=5(等于slow)时,反转[0,4] -> "olleh"
步骤3:反转整个字符串:反转"olleh"->"hello"?不对,我们需要反转整个字符串,但反转整个字符串后还是"hello"?不对,因为单词顺序反转(只有一个单词所以不变),但每个单词被反转了一次,整个字符串又被反转了一次,所以最终应该是:先反转单词(得到"olleh"),再反转整个字符串(得到"hello")?不对,我们要求输出是"hello"(单词顺序反转,但只有一个单词,所以还是hello)。但我们的程序会先反转每个单词(变成"olleh"),然后反转整个字符串(又变回"hello")。所以正确。
但是,如果我们要求反转单词顺序,那么只有一个单词时,反转后还是它自己。所以正确。
但是,我们再看两个单词的情况:输入"hello world" -> 输出"world hello",正确。
另一种情况:输入多个空格,如" ",那么移除空格后,字符串为空,所以后面反转就不需要操作。
因此,我们按照上述步骤编写代码。
注意:我们修改的是原字符串,使用引用传递。
代码实现:
我们按照上述步骤编写。
注意:在移除空格时,我们使用了双指针,并且最后resize了字符串。
但是,我们也可以不使用双指针,而是用标准库函数,但题目要求移除多余空格,所以我们需要自己实现。
完整代码:
*/
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Solution {
public:
void reverseWords(string &s) {
// 步骤1:移除多余空格
int n = s.size();
int slow = 0, fast = 0;
// 跳过开头空格
while (fast < n && s[fast] == ' ') {
fast++;
}
while (fast < n) {
// 复制一个单词
while (fast < n && s[fast] != ' ') {
s[slow] = s[fast];
slow++;
fast++;
}
// 跳过单词后面的空格
while (fast < n && s[fast] == ' ') {
fast++;
}
// 如果后面还有非空格字符,则添加一个空格
if (fast < n) {
s[slow] = ' ';
slow++;
}
}
// 现在slow是新字符串的长度
s.resize(slow);
// 步骤2:反转每个单词
int start = 0;
for (int i = 0; i <= slow; i++) {
// 当遇到空格或字符串结尾时,反转上一个单词
if (i == slow || s[i] == ' ') {
reverseSubstr(s, start, i - 1);
start = i + 1;
}
}
// 步骤3:反转整个字符串
reverseSubstr(s, 0, slow - 1);
}
private:
void reverseSubstr(string &s, int left, int right) {
while (left < right) {
swap(s[left], s[right]);
left++;
right--;
}
}
};
// 测试
int main() {
Solution sol;
string s = " hello world ";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望输出: "world hello"
s = "a good example";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望输出: "example good a"
s = " ";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望输出: "" (空字符串)
s = "hello";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望输出: "hello"
return 0;
}
/*
运行测试:
第一个测试:" hello world " -> 移除空格后为"hello world"(长度为11),反转每个单词后为"olleh dlrow",再反转整个字符串为"world hello"。
第二个测试:"a good example" -> 移除多余空格后为"a good example",反转每个单词:'a'->'a', 'good'->'doog', 'example'->'elpmaxe',得到"a doog elpmaxe",然后反转整个字符串:"elpmaxe doog a" -> "example good a"(注意:单词反转后,每个单词被反转了,再整个反转,所以每个单词又变回原样?不对,我们看:
原始: "a good example"
移除空格后: "a good example"
反转每个单词:
"a" -> "a"
"good" -> "doog"
"example" -> "elpmaxe"
所以字符串变成 "a doog elpmaxe"
反转整个字符串: "elpmaxe doog a" -> 然后我们写出来是 "example" 吗?注意,反转整个字符串时,我们反转的是整个字符串"a doog elpmaxe" -> "example good a" 吗?
反转整个字符串:从第一个字符'a'到最后一个字符'e',反转后:
原:a doog elpmaxe
反转整个:elpmaxe doog a -> 这确实是 "example good a" 吗?注意,字符串是连续的,所以反转整个字符串后得到:"elpmaxe doog a" -> 但是当我们输出时,它显示为:第一个单词是"elpmaxe",第二个单词是"doog",第三个是"a"。但实际上我们希望的是"example good a"。
问题出在哪里?我们反转每个单词时,把每个单词都反转了,然后整个字符串反转后,每个单词又反转了一次,所以每个单词应该变回原来的顺序?不对,我们期望的是单词顺序反转,但每个单词内部顺序不变。
但是我们的步骤:
原始: "a good example"
反转每个单词:得到 "a doog elpmaxe" -> 每个单词内部顺序被反转了
再反转整个字符串:得到 "elpmaxe doog a" -> 这个字符串中,每个单词内部顺序还是反的(如"elpmaxe"而不是"example")
所以,我们的方法错误!
正确的方法应该是:
方法一(之前提到过):
1. 移除多余空格。
2. 反转整个字符串。
3. 反转每个单词。
这样:
原始: "a good example" -> 移除空格后: "a good example"
反转整个字符串: "elpmaxe doog a"
然后反转每个单词:反转"elpmaxe" -> "example",反转"doog"->"good",反转"a"->"a",得到 "example good a"
所以,我们调整步骤:
步骤1:移除多余空格(同上)。
步骤2:反转整个字符串。
步骤3:反转每个单词。
修改代码:
void reverseWords(string &s) {
// 步骤1:移除多余空格(同上)
// 步骤2:反转整个字符串
reverseSubstr(s, 0, s.size()-1);
// 步骤3:反转每个单词
int start = 0;
int n = s.size();
for (int i = 0; i <= n; i++) {
if (i == n || s[i]==' ') {
reverseSubstr(s, start, i-1);
start = i+1;
}
}
}
测试:输入"a good example"
步骤1:移除多余空格 -> "a good example"(假设已经移除多余空格,这里中间多余空格被移除,但原字符串中间只有一个空格?原字符串是"a good example",我们移除后变成"a good example",中间只有一个空格)
步骤2:反转整个字符串 -> "elpmaxe doog a"
步骤3:反转每个单词:
i=0: 到第一个空格(在位置1)?不对,单词"elpmaxe"从0到6(长度为7)?注意,反转整个字符串后,字符串为:"elpmaxe doog a"
我们需要识别单词:当i=0时,直到i=7(空格)?但字符串是:'e','l','p','m','a','x','e',' ','d','o','o','g',' ','a'
所以,第一个单词从0到6(索引),然后空格在索引7。
反转第一个单词:0到6 -> "example"
然后下一个单词从8到11('d','o','o','g')-> 反转后"good"
然后下一个单词从13到13(只有一个字符)-> 反转后还是"a"
得到 "example good a"
注意:在反转整个字符串后,单词之间仍然只有一个空格,所以步骤3中我们仍然以空格为分隔。
因此,我们修改代码为:
步骤1:移除多余空格(同上)
步骤2:反转整个字符串
步骤3:反转每个单词(同上,但此时字符串是反转整个字符串后的字符串)
重新测试只有一个单词的情况:"hello"
步骤1:移除空格后为"hello"
步骤2:反转整个字符串->"olleh"
步骤3:反转每个单词:当i=5(等于n)时,反转[0,4] -> "hello"
所以输出"hello",正确。
测试两个单词:"hello world"
步骤1:移除空格后->"hello world"
步骤2:反转整个字符串->"dlrow olleh"
步骤3:反转每个单词:第一个单词[0,4] -> "world"(反转"dlrow"->"world"),第二个单词[6,10] -> "hello"(反转"olleh"->"hello"),得到"world hello"
但是,注意:反转整个字符串后,字符串是"dlrow olleh",然后我们反转每个单词:
第一个单词:从0到4(因为索引5是空格,所以第一个单词是"dlrow",反转后变成"world")
第二个单词:从6到10("olleh"反转后"hello")
所以整个字符串变成"world hello"
注意:在反转整个字符串后,单词之间空格的位置?在反转整个字符串后,空格也被反转了,但空格本身位置不变?实际上,反转整个字符串后,空格的位置会变到字符串的什么位置?
原始: "hello world" -> 反转整个字符串: "dlrow olleh"
原来空格在索引5(0-based),反转后整个字符串长度为11,空格应该在索引5(因为对称),但这里:
原始: [0:h,1:e,2:l,3:l,4:o,5: ,6:w,7:o,8:r,9:l,10:d]
反转整个: [0:d,1:l,2:r,3:o,4:w,5: ,6:o,7:l,8:l,9:e,10:h]
所以空格在索引5,所以我们在步骤3中,当i=5时,反转第一个单词(0到4)-> "dlrow"反转成"world",然后设置start=6,然后i=11时,反转6到10->"olleh"反转成"hello"。
因此,代码正确。
修改后的完整代码:
*/
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Solution {
public:
void reverseWords(string &s) {
// 步骤1:移除多余空格
int n = s.size();
int slow = 0, fast = 0;
// 跳过开头空格
while (fast < n && s[fast] == ' ') {
fast++;
}
while (fast < n) {
// 复制一个单词
while (fast < n && s[fast] != ' ') {
s[slow] = s[fast];
slow++;
fast++;
}
// 跳过单词后面的空格
while (fast < n && s[fast] == ' ') {
fast++;
}
// 如果后面还有非空格字符,则添加一个空格
if (fast < n) {
s[slow] = ' ';
slow++;
}
}
// 现在slow是新字符串的长度
s.resize(slow);
n = slow; // 更新n
// 步骤2:反转整个字符串
reverseSubstr(s, 0, n - 1);
// 步骤3:反转每个单词
int start = 0;
for (int i = 0; i <= n; i++) {
// 当遇到空格或字符串结尾时,反转上一个单词
if (i == n || s[i] == ' ') {
reverseSubstr(s, start, i - 1);
start = i + 1;
}
}
}
private:
void reverseSubstr(string &s, int left, int right) {
while (left < right) {
swap(s[left], s[right]);
left++;
right--;
}
}
};
// 测试代码不变
/*
再次测试:
输入:s = " hello world "
步骤1:移除多余空格:得到"hello world",长度为11。
步骤2:反转整个字符串:得到 "dlrow olleh"
步骤3:反转每个单词:
i=0~10:
当i=5(空格)时,反转[0,4] -> "dlrow"反转成"world",字符串变为"world olleh"
然后start=6,i=11(等于n)时,反转[6,10] -> "olleh"反转成"hello",字符串变为"world hello"
输出:"world hello",正确。
输入:"a good example"
步骤1:移除多余空格:得到"a good example",注意原字符串中"good"和"example"之间有多个空格,我们只保留一个,所以是"a good example"(长度为13:a,空格,g,o,o,d,空格,e,x,a,m,p,l,e)
步骤2:反转整个字符串:得到 "elpmaxe doog a"
步骤3:反转每个单词:
i=0: 直到i=1(空格)?不对,第一个单词是"elpmaxe",它在反转整个字符串后是连续的,直到下一个空格(在索引7?不对,我们数一下:原始"a good example"反转整个字符串后:
原始:0:a, 1:空格, 2:g,3:o,4:o,5:d,6:空格,7:e,8:x,9:a,10:m,11:p,12:l,13:e -> 反转整个后:从后往前:e,l,p,m,a,x,e,空格,d,o,o,g,空格,a
所以:索引0:e,1:l,2:p,3:m,4:a,5:x,6:e,7:空格,8:d,9:o,10:o,11:g,12:空格,13:a -> 但我们的字符串长度是13(0~12)?不对,移除空格后字符串长度是13吗?
原字符串"a good example":字符数:a(1)+空格(1)+good(4)+空格(1)+example(7) = 1+1+4+1+7=14个字符?不对,字符串长度是:a(1),空格(1),g(1),o(1),o(1),d(1),空格(1),e(1),x(1),a(1),m(1),p(1),l(1),e(1) -> 14个字符(索引0~13)。
移除多余空格后,我们得到:原字符串"a good example" -> 移除后是"a good example",长度是14?不对,因为原字符串中"good"和"example"之间有多个空格,我们移除了多余的空格,所以中间只有一个空格,因此总长度是:1(a)+1(空格)+4(good)+1(空格)+7(example)=14?不对,1+1+4+1+7=14,但注意我们移除了两个空格(原字符串中三个空格变成1个,移除了2个空格),所以新字符串长度应该是原长度-2,即14-2=12?不对,原字符串长度是14(包括两个空格?不对,原字符串是"a good example":a,空格,g,o,o,d,空格,空格,空格,e,x,a,m,p,l,e -> 总字符数:a(1),空格(1),g(1),o(1),o(1),d(1),空格(1),空格(1),空格(1),e(1),x(1),a(1),m(1),p(1),l(1),e(1) -> 16个字符?不对,我们写一下:
"a good example" ->
a:1
空格:1
g,o,o,d:4
空格:3(连续三个)
e,x,a,m,p,l,e:7
总共:1+1+4+3+7=16
移除多余空格后:a,空格,good,空格,example -> 1+1+4+1+7=14
所以移除后字符串长度14(索引0~13)。
反转整个字符串:从0~13反转:从后往前:e,l,p,m,a,x,e,空格,d,o,o,g,空格,a -> 所以字符串变为:"elpmaxe doog a"(注意,这里最后是空格a,但实际是a在最后?不对,反转整个字符串后,最后一个字符是原字符串的第一个字符'a',所以整个字符串反转后:
原:0:a,1: ,2:g,3:o,4:o,5:d,6: ,7:e,8:x,9:a,10:m,11:p,12:l,13:e
反转后:0:e,1:l,2:p,3:m,4:a,5:x,6:e,7: ,8:d,9:o,10:o,11:g,12: ,13:a
步骤3:反转每个单词:
第一个单词:从0到6(因为索引7是空格),反转[0,6]:子串"elpmaxe"反转成"example"
第二个单词:从8到11(因为索引12是空格),反转[8,11]:子串"doog"反转成"good"
第三个单词:从13到13(只有一个字符'a',反转后还是'a')
所以得到:"example" + 空格 + "good" + 空格 + "a" -> 但这不是我们想要的,我们想要"example good a",但这里第三个单词是"a",而原来第一个单词是"a",现在变成了最后一个单词,但顺序是"example good a",正确。
但是,我们期望输出是"example good a",所以正确。
因此,我们使用第二种步骤。
注意:在反转整个字符串后,原来的空格位置会变成新字符串中的空格位置,所以我们在反转单词时,仍然可以用空格作为分隔。
代码修改后,重新运行测试。
最后,我们考虑空字符串和全空格字符串:
全空格字符串:如" ",在步骤1中,fast会跳过开头空格,然后fast等于n,所以slow=0,然后resize(0)后,字符串为空。然后步骤2:反转整个字符串(n=0,不操作),步骤3:反转每个单词(循环不会执行任何反转操作)。所以返回空字符串,正确。
因此,代码完成。
注意:我们使用了标准库的swap,也可以自己写交换函数。
我们将代码整理并测试。
测试用例:
1. " hello world " -> "world hello"
2. "a good example" -> "example good a"
3. " " -> ""
4. "hello" -> "hello"
5. " one " -> "one"
测试第五个例子:" one "
步骤1:移除空格:得到"one"(长度为3)
步骤2:反转整个字符串->"eno"
步骤3:反转每个单词:当i=3时,反转[0,2]->"one"
输出:"one"
但是,我们希望反转单词顺序,只有一个单词,所以输出"one",正确。
因此,我们输出完整代码。
注意:由于题目要求用C++,我们使用std::string。
另外,我们也可以使用标准库的reverse,但为了展示原理,我们自己实现了reverseSubstr。
最终代码:
*/
#include <iostream>
#include <string>
using namespace std;
class Solution {
public:
void reverseWords(string &s) {
// 移除多余空格
int n = s.size();
int slow = 0, fast = 0;
// 跳过开头空格
while (fast < n && s[fast] == ' ') {
fast++;
}
while (fast < n) {
// 复制一个单词
while (fast < n && s[fast] != ' ') {
s[slow++] = s[fast++];
}
// 跳过单词后面的空格(连续空格)
while (fast < n && s[fast] == ' ') {
fast++;
}
// 如果后面还有单词,则添加一个空格
if (fast < n) {
s[slow++] = ' ';
}
}
// 调整字符串大小
s.resize(slow);
n = slow; // 更新n
// 反转整个字符串
reverseSubstr(s, 0, n - 1);
// 反转每个单词
int start = 0;
for (int i = 0; i <= n; i++) {
if (i == n || s[i] == ' ') {
reverseSubstr(s, start, i - 1);
start = i + 1;
}
}
}
private:
void reverseSubstr(string &s, int left, int right) {
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
};
// 测试主函数
int main() {
Solution sol;
string s;
s = " hello world ";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望: "world hello"
s = "a good example";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望: "example good a"
s = " ";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望: ""
s = "hello";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望: "hello"
s = " one ";
sol.reverseWords(s);
cout << "\"" << s << "\"" << endl; // 期望: "one"
return 0;
}
/*
运行测试,应该通过。
注意:在移除空格的部分,我们确保了单词之间只有一个空格,且开头结尾无空格。
因此,我们完成了程序。
*/</think>以下是一个用C++编写的程序,用于反转字符串中的单词顺序并移除多余空格:
```cpp
#include <iostream>
#include <string>
using namespace std;
class Solution {
public:
void reverseWords(string &s) {
// 1. 移除多余空格
int n = s.size();
int slow = 0, fast = 0;
// 跳过开头空格
while (fast < n && s[fast] == ' ') fast++;
while (fast < n) {
// 复制单词字符
while (fast < n && s[fast] != ' ') {
s[slow++] = s[fast++];
}
// 跳过连续空格
while (fast < n && s[fast] == ' ') fast++;
// 添加单词间单空格(如果还有单词)
if (fast < n) {
s[slow++] = ' ';
}
}
s.resize(slow); // 调整字符串大小
n = slow; // 更新长度
// 2. 反转整个字符串
reverseRange(s, 0, n - 1);
// 3. 反转每个单词
int start = 0;
for (int i = 0; i <= n; i++) {
if (i == n || s[i] == ' ') {
reverseRange(s, start, i - 1);
start = i + 1;
}
}
}
private:
// 反转子串 [left, right]
void reverseRange(string &s, int left, int right) {
while (left < right) {
swap(s[left], s[right]);
left++;
right--;
}
}
};
// 测试用例
int main() {
Solution sol;
string test;
test = " hello world ";
sol.reverseWords(test);
cout << "测试1: \"" << test << "\"" << endl; // 输出: "world hello"
test = "the sky is blue";
sol.reverseWords(test);
cout << "测试2: \"" << test << "\"" << endl; // 输出: "blue is sky the"
test = " ";
sol.reverseWords(test);
cout << "测试3: \"" << test << "\"" << endl; // 输出: ""
test = "single";
sol.reverseWords(test);
cout << "测试4: \"" << test << "\"" << endl; // 输出: "single"
test = "a b c d";
sol.reverseWords(test);
cout << "测试5: \"" << test << "\"" << endl; // 输出: "d c b a"
return 0;
}
```
### 程序说明
1. **移除多余空格**:
- 使用双指针技巧(`slow`和`fast`):
- `fast`指针跳过开头空格和连续空格
- 只保留单词间单个空格
- 最后调整字符串大小
2. **反转整个字符串**:
- 通过`reverseRange()`函数实现整个字符串的反转
- 例如`"hello world"`变为`"dlrow olleh"`
3. **反转每个单词**:
- 遍历字符串识别单词边界(空格或结尾)
- 对每个单词单独反转
- 例如`"dlrow olleh"`变为`"world hello"`
### 时间复杂度
- **$O(n)$时间复杂度**:所有操作(移除空格、整体反转、单词反转)均只需单次遍历
- **$O(1)$空间复杂度**:原地修改字符串,无需额外空间
### 测试用例
| 输入 | 输出 | 说明 |
|------|------|------|
| `" hello world "` | `"world hello"` | 处理首尾/中间多余空格 |
| `"the sky is blue"` | `"blue is sky the"` | 多单词反转 |
| `" "` | `""` | 全空格返回空 |
| `"single"` | `"single"` | 单单词不变 |
| `"a b c d"` | `"d c b a"` | 短单词处理 |
### 相关问题
1. 如何修改程序以保留标点符号的位置(如`"hello, world!"`变为`"world! hello,"`)?
2. 如果要求不使用额外空间(包括`swap`函数),如何优化反转操作?
3. 如何扩展程序以处理制表符`\t`等其他空白字符?
4. 在内存受限环境中,如何优化该算法以适应超大字符串?
5. 如何修改程序使其支持双向迭代器(如链表存储的字符串)?
> 引用说明:本实现参考了字符串处理中的双指针技巧和分段反转思想,这是处理原地字符串操作的常用范式[^4]。