面试经典150题[021]:反转字符串中的单词(LeetCode 151)

反转字符串中的单词(LeetCode 151)

题目链接:反转字符串中的单词(LeetCode 151)
难度:中等

1. 题目描述

给你一个字符串 s,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将单词分隔开。
返回单词顺序颠倒且单词之间用 单个空格 连接的结果字符串。

注意

  • 输入字符串 s 中可能存在前导空格、尾随空格或单词间的多个空格。
  • 返回结果中,单词间仅用单个空格分隔,不包含任何额外空格。

要求

  • 1 <= s.length <= 10^4
  • s 包含英文大小写字母、数字和空格 ’ ’
  • s 中至少存在一个单词

示例

输入: s = "the sky is blue"
输出: "blue is sky the"
输入: s = "  hello world  "
输出: "world hello"
输入: s = "a good   example"
输出: "example good a"

进阶:如果字符串在使用的编程语言中是可变数据类型,尝试使用 O(1) 额外空间复杂度的原地解法。

2. 问题分析

2.1 规律

  • 需要反转字符串中单词的顺序,但保持每个单词内部字符的顺序。
  • 输入字符串可能包含多余空格(前导、尾随、单词间多个空格),输出需保证单词间只有单个空格。
  • 核心问题:如何高效处理空格并反转单词顺序?
  • 进阶要求 O(1) 空间复杂度,意味着不能使用额外的数据结构(如数组存储单词),需要在原字符串上操作。

2.2 解题思路

思路 1:分步处理(非原地,O(n) 空间)
  1. 清理空格:将字符串按空格分割成单词数组,过滤掉空字符串。
  2. 反转单词顺序:将单词数组反转。
  3. 拼接结果:用单个空格连接单词,生成最终字符串。

此方法简单直观,但需要额外空间存储单词数组,不满足进阶要求。

思路 2:原地解法(O(1) 空间,针对可变字符串)
  1. 反转整个字符串:先将整个字符串的字符反转(包括空格)。
  2. 反转每个单词:遍历反转后的字符串,找到每个单词的起止位置,单独反转单词内部字符。
  3. 清理多余空格:在原字符串上移除前导、尾随空格及单词间多余空格,调整为单个空格。

此方法适合可变字符串(如 C++ 的 std::string 或字符数组),满足进阶要求。

贪心思路(基于思路 2)
  • 反转整个字符串:将字符串视为字符序列,直接反转。
  • 逐个反转单词:基于空格识别单词边界,反转每个单词。
  • 清理空格:从左到右扫描,保留单词间的单个空格,移除多余空格。
  • 关键点:所有操作都在原字符串上进行,额外空间仅为常数级别。

2.3 示例

s = " hello world " 为例:

  • 步骤 1:反转整个字符串
    原字符串:" hello world "
    反转后:" dlrow olleh "
  • 步骤 2:反转每个单词
    找到单词 "dlrow",反转为 "world"
    找到单词 "olleh",反转为 "hello"
    结果:" world hello "
  • 步骤 3:清理空格
    移除前导、尾随空格,单词间保留单个空格:
    结果:"world hello"

3. 代码实现

Python(思路 1:非原地,简单实现)

class Solution:
    def reverseWords(self, s: str) -> str:
        # 按空格分割,过滤空字符串
        words = [word for word in s.split() if word]
        # 反转单词数组
        words.reverse()
        # 用单个空格连接
        return " ".join(words)

C++(思路 2:原地解法,O(1) 空间)

class Solution {
public:
    string reverseWords(string s) {
        int n = s.size();
        // 步骤 1:反转整个字符串
        reverse(s.begin(), s.end());
        
        // 步骤 2:反转每个单词
        int i = 0;
        while (i < n) {
            // 跳过空格
            while (i < n && s[i] == ' ') i++;
            if (i == n) break;
            // 找到单词的起始和结束位置
            int start = i;
            while (i < n && s[i] != ' ') i++;
            int end = i - 1;
            // 反转单词
            while (start < end) {
                swap(s[start++], s[end--]);
            }
        }
        
        // 步骤 3:清理空格
        int write = 0; // 写入位置
        i = 0; // 读取位置
        while (i < n) {
            // 跳过前导空格
            while (i < n && s[i] == ' ') i++;
            if (i == n) break;
            // 写入单词
            if (write > 0) s[write++] = ' '; // 单词前加单个空格
            while (i < n && s[i] != ' ') {
                s[write++] = s[i++];
            }
        }
        // 调整字符串长度
        s.resize(write);
        return s;
    }
};

4. 复杂度分析

思路 1(Python 非原地)

  • 时间复杂度:O(n),其中 n 是字符串长度。分割、反转、拼接操作均为线性时间。
  • 空间复杂度:O(n),需要存储单词数组。

思路 2(C++ 原地)

  • 时间复杂度:O(n)
    • 反转整个字符串:O(n)
    • 反转每个单词:总计 O(n)
    • 清理空格:O(n)
  • 空间复杂度:O(1),仅使用常数额外空间(不计输入字符串的修改)。

5. 总结

  • 简单解法:分割单词、反转、拼接,适合快速实现,但空间复杂度为 O(n)。
  • 原地解法:反转整个字符串 → 反转单词 → 清理空格,满足进阶要求,空间复杂度 O(1)。
  • 关键点
    • 处理多余空格是重点,需确保单词间仅一个空格。
    • 原地解法依赖字符串的可变性,适合 C++ 等语言,Python 字符串不可变无法完全实现 O(1) 空间。
  • 扩展:可参考类似问题,如 LeetCode 186(反转字符串中的单词 II,仅限字符数组)。

复习

面试经典150题[006]:旋转数组(LeetCode 189)
面试经典150题[007]:买卖股票的最佳时机(LeetCode 121)
面试经典150题[014]:加油站(LeetCode 134)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洪哥等风来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值