【字符串】leetcode151.翻转字符串里的单词(C/C++/Java/Python/Js)


综合考察字符串操作的好题。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值