-
题目:剑指Offer58-Ⅰ.翻转单词顺序
输入一个字符串s表示一句若干单词构成的一句话;
s可能前面,后面,或单词之间有若干空格;
需要将s的每个单词顺序翻转,但每个单词自身的字母顺序不能翻转;
最后得到的字符串首尾不能有空格,且字母之间的空格只留一个; -
思路:
1.快慢双指针:时间O(n):只需快指针从尾遍历到头,空间O(n):额外需要一个字符串res,最坏情况下,s没有多余空格,因此res的大小和s相同;
从后往前划分单词,每找到一个单词[ i + 1,j ]就放到res后面,并添加一个空格作为单词之间的空隙;
res的最后一个单词的后面多了一个空格,需要删掉;但有个特例是s本身只有空格,不存在任何一个单词,因此res也没有添加过单词和作为空隙的一个空格,因此仅当res不为空时,才需要删除空格操作;
class Solution {
public:
string reverseWords(string s) {
int n = s.size();
if (n == 0) return s;
string res;
int i = n - 1, j = n - 1;
while (i >= 0) {
while (i >= 0 && s[i] == ' ') --i;
if (i >= 0) j = i;//进入这个分支,说明找到了一个单词末尾
else continue;//说明找不到单词了
while (i >= 0 && s[i] != ' ') --i;//确定这个单词的范围[i + 1, j]
for (int k = i + 1; k <= j; ++k) res.push_back(s[k]);
res.push_back(' ');//每个单词后面补一个' '
}
//特殊情况:如果字符串s一个单词都没有全是空格,那么res为空,就不需要删除' '
if (!res.empty()) res.pop_back();//最后一个单词的后面的' '是多余的;
return res;
}
};
2.双指针原地修改:时间O(n),空间O(1):因为是原地修改,因此没有使用额外空间
在s上原地修改;
先去除首尾,中间的多余空格,并用resize保留有用的部分;
整体翻转 + 每个单词局部翻转:以此实现所有单词顺序翻转,但每个单词内的字母顺序不变;
class Solution {
public:
string reverseWords(string s) {
int n = s.size();
if (n == 0) return s;
int i = 0, j = 0;//快指针i,慢指针j
while (i < n && s[i] == ' ') ++i;//跳过开头的空格
if (i == n) return "";//说明s只有空格,没有单词
while (i < n) {//去掉单词间多余的空格,每个单词后面若有多个空格,只剩下一个
if (i > 0 && s[i] == s[i -1] && s[i] == ' ') {
++i;
continue;
}
s[j++] = s[i++];
}
//若最后一个单词后面本身没空格,自然j停在最后一个单词的下个位置;
//若最后一个单词后面有若干空格,自然j停在最后一个单词的下下个位置,因为下个位置的空格被保留
if (s[j - 1] == ' ') s.resize(j - 1);
else s.resize(j);
reverse(s.begin(), s.end());//整体翻转
i = 0, j = 0;//重新初始化i,j,n
n = s.size();
while (i < n) {//每个单词局部翻转
while (i < n && s[i] != ' ') ++i;//i停在一个单词的下位置,即划分出一个单词[j, i - 1]
reverse(s.begin() + j, s.begin() + i);
i++;//跳过单词间的那个空格,指向下个单词的首字母
j = i;//慢指针j指向单词首字母,下次循环中i去确定该单词的截止位置
}
return s;
}
};