151. 反转字符串中的单词
给你一个字符串 s
,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意: 输入字符串 s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
输入: s = "the sky is blue"
输出:"blue is sky the"
示例 2:
输入: s = " hello world "
输出:"world hello"
解释: 反转后的字符串中不能存在前导空格和尾随空格。
示例 3:
输入: s = "a good example"
输出:"example good a"
解释: 如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
提示:
- 1 ≤ s . l e n g t h ≤ 1 0 4 1 \leq s.length \leq 10^4 1≤s.length≤104
s
包含英文大小写字母、数字和空格' '
s
中 至少存在一个 单词
进阶: 如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1)
额外空间复杂度的 原地 解法。
解法一(快慢双指针)
思路分析:
- 使用快慢双指针法,快慢指针从后往前遍历 如此先找到的单词则放在返回结果的前面 实现反转;
- 快指针指向一个单词的起始位置,慢指针则指向一个单词的末尾位置
- 每当找到一个单词后,则将单词保存到
ans
中,然后继续移动快慢指针
实现代码如下:
class Solution {
public String reverseWords(String s) {
int n = s.length(); // 字符串s的长度
if (n == 1)
return s; // 当只有一个单词时 无需反转
StringBuffer ans = new StringBuffer(); // 记录反转后的字符串
int slow = n-1; // 慢指针 记录一个单词的末尾位置
while (slow >= 0 && s.charAt(slow) == ' ')
-- slow; // 跳过后置空格 并指向第一个单词的末尾位置
int fast = slow; // 快指针 指向单词的起始位置
while (fast >= 0) {
// 移动快指针到 一个单词的起始
while (fast >= 0 && s.charAt(fast) != ' ')
-- fast;
// 找到一个单词 则将单词保存到返回值中
ans.append(s, fast+1, slow+1).append(' ');
// 继续移动慢指针 到下一个单词的末尾位置
slow = fast;
while (slow >= 0 && s.charAt(slow) == ' ')
-- slow;
fast = slow; // 更新快指针下一次移动的起始位置
}
ans.deleteCharAt(ans.length()-1); // 删除末尾多余的空格
return new String(ans);
}
}
提交结果如下:
解答成功:
执行耗时:3 ms,击败了90.82% 的Java用户
内存消耗:40.6 MB,击败了73.99% 的Java用户
复杂度分析:
- 时间复杂度:
O
(
n
)
O(n)
O(n),n为字符串s的长度,快慢指针一共遍历字符串一次,不包括
ans.append(s, fast+1, slow+1)
的时间复杂度 - 空间复杂度: O ( 1 ) O(1) O(1),不计算返回值所消耗的空间,则双指针花费的空间复杂度为 O ( 1 ) O(1) O(1)
解法二(不使用Java内置函数)
思路分析:
- 先去除字符串前后空格和中间多余的空格,参考移除元素,使用快慢双指针法
- 然后反转整个字符串 使用相向双指针进行反转
- 最后反转每个单词
- 使用
s.toCharArray()
获取一个可修改的字符数组,在该数组的基础上进行修改
实现代码如下:
class Solution {
public String reverseWords(String s) {
int n = s.length(); // 字符串s的长度
if (n == 1)
return s; // 当只有一个单词时 无需反转
char[] str = s.toCharArray(); // 将字符串转化为字符数组
str = removeExtraSpaces(str); // 去除多余的空格
// 反转整个字符串
reverseString(0, str.length-1, str);
// 反转新字符串数组中的 每个单词
int start = 0; // 单词的起始索引
for (int end = 0; end <= str.length; ++end) {
if (end == str.length || str[end] == ' ') { // end==str.length时 反转末尾的单词
reverseString(start, end-1, str);
start = end + 1;
}
}
return new String(str);
}
// 相向双指针 反转字符串
private void reverseString(int left, int right, char[] str) {
while (left < right) {
// 交换左右指针 指向的数组元素
str[left] ^= str[right];
str[right] ^= str[left];
str[left] ^= str[right];
// 移动左右指针
++ left;
-- right;
}
}
// 去除字符串 前后及中间的多余空格
private char[] removeExtraSpaces(char[] str) {
// 使用快慢双指针法
int n = str.length; // 数组长度
int slow = 0;
for (int fast = 0; fast < str.length; ++fast) {
if (str[fast] != ' ') { // 快指针开始遍历一个单词
if (slow != 0) { // 若此时遍历的单词不是第一个单词 则需要用空格隔开
str[slow++] = ' ';
}
while (fast < n && str[fast] != ' ') // 快指针遍历到一个单词的末尾
str[slow++] = str[fast++];
}
}
// 去除多余空格后的数组 长度为 slow
// 因此需要返回新的数组
char[] newStr = new char[slow];
System.arraycopy(str, 0, newStr, 0, slow);
return newStr;
}
}
提交结果如下:
解答成功:
执行耗时:2 ms,击败了97.62% 的Java用户
内存消耗:40.7 MB,击败了66.51% 的Java用户
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),移除多余空格和反转字符串的时间复杂度均为 O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n),因为需要改变字符串长度,需要使用新的数组,所以消耗空间为 O ( n ) O(n) O(n)