leetcode151.翻转字符串里的单词
综合考察字符串操作的好题。
1 题目
给你一个字符串 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.length <= 104
s 包含英文大小写字母、数字和空格 ’ ’
s 中 至少存在一个 单词
进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1) 额外空间复杂度的 原地 解法。
2 思路
2.1 整体思路
Carl老师视频讲解:
字符串复杂操作拿捏了! | LeetCode:151.翻转字符串里的单词
如果使用库函数例如split之类的分隔单词,那这道题就失去了大半价值。
我们从全新思路开始:
使用O(1)额外空间复杂度的原地解法出发:
将整个字符串反转,再将单词反转。
解题思路:
移除多余空格;
将整个字符串反转;
将每个单词反转。
举个例子,源字符串为:"the sky is blue "
- 移除多余空格 : “the sky is blue”
- 字符串反转:“eulb si yks eht”
- 单词反转:“blue is sky the”
这样我们就完成了翻转字符串里的单词。
2.2 细节注意–移除多余空格
如果采用这样的逻辑:遍历字符串,遇到空格就erase。
void removeExtraSpaces(string& s) {
for (int i = s.size() - 1; i > 0; i--) {
if (s[i] == s[i - 1] && s[i] == ' ') {
s.erase(s.begin() + i);
}
}
// 删除字符串最后面的空格
if (s.size() > 0 && s[s.size() - 1] == ' ') {
s.erase(s.begin() + s.size() - 1);
}
// 删除字符串最前面的空格
if (s.size() > 0 && s[0] == ' ') {
s.erase(s.begin());
}
}
这时候时间复杂度还是O(n)吗?
其实已经不是了。
一个erase本来就是O(n)的操作。
erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码时间复杂度为O(n2)。
之前也有提到双指针法来降低时间复杂度:
使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。
版本一:
思路:先移除字符串前的空格,再移除中间的,最后移除字符串末尾部分。
//版本一
void removeExtraSpaces(string& s) {
int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针
// 去掉字符串前面的空格
while (s.size() > 0 && fastIndex < s.size() && s[fastIndex] == ' ') {
fastIndex++;
}
for (; fastIndex < s.size(); fastIndex++) {
// 去掉字符串中间部分的冗余空格
if (fastIndex - 1 > 0
&& s[fastIndex - 1] == s[fastIndex]
&& s[fastIndex] == ' ') {
continue;
} else {
s[slowIndex++] = s[fastIndex];
}
}
if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') { // 去掉字符串末尾的空格
s.resize(slowIndex - 1);
} else {
s.resize(slowIndex); // 重新设置字符串大小
}
}
优化版本,思路与【数组】leetcode27.移除元素(C/C++/Java/Js)逻辑一样:
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0; //整体思想参考https://programmercarl.com/0027.移除元素.html
for (int i = 0; i < s.size(); ++i) { //
if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); //slow的大小即为去除多余空格后的大小。
}
3 代码
3.1 C++版本
class Solution {
public:
//反转函数reverse,左闭右闭写法
void reverse(string &s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--)
swap(s[i], s[j]);
}
//移除多余空格函数removeExtraSpaces,并在相邻单词之间添加空格
void removeExtraSpaces(string& s) {
int slow = 0;
for (int fast = 0; fast < s.size(); fast++) {
if (s[fast] != ' ') { //遇到非空格处理
if (slow > 0) //如果不是第一个位置,给单词间添加空格
s[slow++] = ' ';
while (fast < s.size() && s[fast] != ' ') //将单词读完整
s[slow++] = s[fast++];
}
}
s.resize(slow); //slow的大小为去除多余空格之后的大小
}
string reverseWords(string s) {
removeExtraSpaces(s); //去除多余空格
reverse(s, 0, s.size() - 1); //反转整个s
int start = 0;
for (int i = 0; i <= s.size(); i++) {
if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,进行单词反转
reverse(s, start, i - 1);
start = i + 1; //更新下一个单词的开始下标
}
}
return s;
}
};
3.2 C版本
void cmp(char * s, int i, int j)//反转单词
{
while(i < j)
{
s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];
i++;
j--;
}
return;
}
char * reverseWords(char * s){
int len = strlen(s);
int left = 0;
int right = 0;
for(left, right; right < len; right++)//移除多余空格
{
if((left == 0 && s[right] == ' ') || (left != 0 && s[left-1] == ' ' && s[right] == ' '))
{
continue;
}
s[left++] = s[right];
}
if(s[left-1] == ' ')//构造最后结束符,也可以构造为'空格',后面补上'\0'
{
s[--left] = '\0';
}
else
{
s[left] = '\0';
}
cmp(s, 0, left-1);//将整个字符串反转
for(int i = 0, j = 0; i <= left; i++)//将每个单词反转
{
if(s[i] == ' ' || s[i] == '\0')//s[i] == '\0'最后一个单词,如果最后构造为'空格'就需要单独判断了
{
cmp(s, j, i-1);
j = i+1;
}
}
return s;
}
3.3 Java版本
class Solution {
//用 char[] 来实现 String 的 removeExtraSpaces,reverse 操作
public String reverseWords(String s) {
char[] chars = s.toCharArray();
//1.去除首尾以及中间多余空格
chars = removeExtraSpaces(chars);
//2.整个字符串反转
reverse(chars, 0, chars.length - 1);
//3.单词反转
reverseEachWord(chars);
return new String(chars);
}
//1.用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解
public char[] removeExtraSpaces(char[] chars) {
int slow = 0;
for (int fast = 0; fast < chars.length; fast++) {
//先用 fast 移除所有空格
if (chars[fast] != ' ') {
//在用 slow 加空格。 除第一个单词外,单词末尾要加空格
if (slow != 0)
chars[slow++] = ' ';
//fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
while (fast < chars.length && chars[fast] != ' ')
chars[slow++] = chars[fast++];
}
}
//相当于 c++ 里的 resize()
char[] newChars = new char[slow];
System.arraycopy(chars, 0, newChars, 0, slow);
return newChars;
}
//双指针实现指定范围内字符串反转,可参考字符串反转题解
public void reverse(char[] chars, int left, int right) {
if (right >= chars.length) {
System.out.println("set a wrong right");
return;
}
while (left < right) {
chars[left] ^= chars[right];
chars[right] ^= chars[left];
chars[left] ^= chars[right];
left++;
right--;
}
}
//3.单词反转
public void reverseEachWord(char[] chars) {
int start = 0;
//end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置
for (int end = 0; end <= chars.length; end++) {
// end 每次到单词末尾后的空格或串尾,开始反转单词
if (end == chars.length || chars[end] == ' ') {
reverse(chars, start, end - 1);
start = end + 1;
}
}
}
}
3.4 Python版本
class Solution:
def reverseWords(self, s: str) -> str:
# method 1 - Rude but work & efficient method.
s_list = [i for i in s.split(" ") if len(i) > 0]
return " ".join(s_list[::-1])
# method 2 - Carlo's idea
def trim_head_tail_space(ss: str):
p = 0
while p < len(ss) and ss[p] == " ":
p += 1
return ss[p:]
# Trim the head and tail space
s = trim_head_tail_space(s)
s = trim_head_tail_space(s[::-1])[::-1]
pf, ps, s = 0, 0, s[::-1] # Reverse the string.
while pf < len(s):
if s[pf] == " ":
# Will not excede. Because we have clean the tail space.
if s[pf] == s[pf + 1]:
s = s[:pf] + s[pf + 1:]
continue
else:
s = s[:ps] + s[ps: pf][::-1] + s[pf:]
ps, pf = pf + 1, pf + 2
else:
pf += 1
return s[:ps] + s[ps:][::-1] # Must do the last step, because the last word is omit though the pointers are on the correct positions
3.5 JavaScript版本
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function(s) {
// 字符串转数组
const strArr = Array.from(s);
// 移除多余空格
removeExtraSpaces(strArr);
// 翻转
reverse(strArr, 0, strArr.length - 1);
let start = 0;
for(let i = 0; i <= strArr.length; i++) {
if (strArr[i] === ' ' || i === strArr.length) {
// 翻转单词
reverse(strArr, start, i - 1);
start = i + 1;
}
}
return strArr.join('');
};
// 删除多余空格
function removeExtraSpaces(strArr) {
let slowIndex = 0;
let fastIndex = 0;
while(fastIndex < strArr.length) {
// 移除开始位置和重复的空格
if (strArr[fastIndex] === ' ' && (fastIndex === 0 || strArr[fastIndex - 1] === ' ')) {
fastIndex++;
} else {
strArr[slowIndex++] = strArr[fastIndex++];
}
}
// 移除末尾空格
strArr.length = strArr[slowIndex - 1] === ' ' ? slowIndex - 1 : slowIndex;
}
// 翻转从 start 到 end 的字符
function reverse(strArr, start, end) {
let left = start;
let right = end;
while(left < right) {
// 交换
[strArr[left], strArr[right]] = [strArr[right], strArr[left]];
left++;
right--;
}
}
4 总结
移除空格的思路与【数组】leetcode27.移除元素(C/C++/Java/Js)逻辑一样。
实现反转字符串的功能,支持反转字符串子区间参照【字符串】leetcode344.反转字符串(C/C++/Java/Python/Js)和【字符串】leetcode541. 反转字符串II(C/C++/Java/Python/Js)。
Carl老师视频讲解:
字符串复杂操作拿捏了! | LeetCode:151.翻转字符串里的单词
By – Suki 2023/2/5