题目:151.翻转字符串里的单词
给定一个字符串,逐个翻转字符串中的每个单词。不能存在任何"多余的"空格
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
整体思路
如果使用额外空间本题难度就很低了,下面讨论如何不使用额外空间解决
这里和上一题一样,实际上是考察对字符串(或者说数组)本身进行变换的能力。首先对这类不熟悉的题目心中要有信念:我们第一优先级的思路一定是往我们熟知的手法靠拢,是由新题目找老手法,而不是临场创造手法。
这样就能想到这个思路:将字符串整体反转,再将每个单词逐个反转即可。如果没有多余空格,上述操作会非常简单。但这题另一个难点就是处理多余空格。
如何去掉多余空格
既然是"去掉"空格,首先想到的就是先判断空格是否多余,然后直接删除多余空格。这个操作会用到erase()
。但仔细想想就能发现,erase()
的时间复杂度是O(n),因为它需要重新移动一部分元素来保证元素连续。这样的话整体复杂度又被拉到O(n2)了
我们发现删除所有多余空格时,需要移动的元素方向是相同的,都是向前移动。以最后一个元素为例,如果它的终点是下标为0的位置,那么使用erase()
它最终需要移动n次。但实际上我们只需要进行一次移动就可以把它放在正确的位置。
因此,我们的思路可以转变一下:我们以单词为单位将它们前移,并在移动过程中跳过多余的空格即可。具体来说:
设置两个指针start和end,用于标识当前处理的单词的开头和结尾。
设置一个指针idx,从头到尾遍历一遍字符串,标识当前正确的空位
idx置0,start和end去遍历寻找下一个单词的开头结尾
idx为0时,正在处理第一个单词。此时直接把start和end之间的字符赋值到idx开始的等长部分
idx不为0时候,先为idx赋值空格,表示单词之间的空格。再把start和end之间的字符赋值到idx+1开始的等长部分
令end++,再令start = end,然后去寻找下一个单词
最后多余的空格都会出现在字符串的末尾,通过
resize()
或erase()
处理。
void removeSpace(string& s) {
int start = 0; int end = 0; int n = s.size();
int idx = 0;
while (end < n) {
//while每次循环都是处理一个新的单词,所以除了第一个单词之外都需要在开头加上空格
if (idx != 0) {
s[idx++] = ' ';
}
//找到下一个单词的起点
while (start < n && s[start] == ' ') {
start++;
}
end = start;
//从start开始遍历这个单词,将每个字符依次赋值给idx之后的部分
while (end < n && s[end] != ' ') {
s[idx++] = s[end++];
}
//更新start,准备下一轮处理
start = end;
}
//删除集中在尾部的多余空格
s.erase(s.begin() + idx, s.end());
}
反转
接下来的操作就很简单了,把去掉空格之后的字符串整体反转再按单词反转即可:
void removeSpace(string& s) {
int start = 0; int end = 0; int n = s.size();
int idx = 0;
while (end < n) {
//while每次循环都是处理一个新的单词,所以除了第一个单词之外都需要在开头加上空格
if (idx != 0) {
s[idx++] = ' ';
}
//找到下一个单词的起点
while (start < n && s[start] == ' ') {
start++;
}
end = start;
//从start开始遍历这个单词,将每个字符依次赋值给idx之后的部分
while (end < n && s[end] != ' ') {
s[idx++] = s[end++];
}
//更新start,准备下一轮处理
start = end;
}
//删除集中在尾部的多余空格
s.erase(s.begin() + idx, s.end());
}
string reverseWords(string s) {
removeSpace(s); //去除多余空格,保证单词之间之只有一个空格,且字符串首尾没空格。
reverse(s.begin(),s.end());
int start = 0; //removeSpace后保证第一个单词的开始下标一定是0。
for (int i = 0; i <= s.size(); ++i) {
if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,说明一个单词结束。进行翻转。
reverse(s.begin()+start,s.begin()+i); //翻转,注意是左闭右闭 []的翻转。
start = i + 1; //更新下一个单词的开始下标start
}
}
return s;
}