题目描述

✅ 剑指 Offer 58 - I. 翻转单词顺序

image-20261001230752587

image-20260928190002660

image-20260928190002661

题意分析

将单词的先后顺序反转,单词内部的字符顺序保持不变,标点也作为所在单词的一部分保留。输出不含首尾空格,单词之间只放一个空格。

要先输出原串最后一个单词,因此可以直接从右向左寻找单词,每次取出完整片段,而不必先拆分全部单词或反转所有字符。

解法:从右向左双指针扫描

核心思路

[!blue]

right 从字符串末尾开始。先向左跳过连续空格,让它停在尚未处理的最后一个单词末尾;如果已经小于 0,说明没有单词可取,扫描结束。

然后令 left = right,继续向左寻找空格或字符串边界。循环停止时,left 指向单词前的空格,或者等于 -1,所以完整单词位于半开区间 [left+1, right+1)。扫描方向虽然向左,追加的仍是原字符串中的整段,因此不会改变单词内部的顺序。

每次提取的都是剩余部分最右边的单词,依次追加就得到反转后的词序。取完后令 right = left,下一轮从当前单词左侧继续;指针不会向右退回,也不会遗漏任何单词。

只有答案中已有单词时,才在新单词前添加一个空格。这样第一个单词前没有空格,任意两个单词间恰好一个空格,最后一个单词后也不需要再清理。原串中的多余空格都由跳过空格的过程处理。

解题步骤

  1. 建立结果缓冲区,令 right = s.length - 1。
  2. 在 right >= 0 的前提下跳过空格;若越过开头,直接结束。
  3. 从 right 向左移动 left,直到遇到空格或到达 -1,确定当前单词的边界。
  4. 若结果非空,先追加一个空格;随后追加原串中的 [left+1, right+1)。
  5. 令 right = left,重复处理剩余部分,最后返回结果。空字符串或全空格字符串都不会追加任何单词,结果为空串。

代码实现

class Solution {
    public String reverseWords(String s) {
        StringBuilder ans = new StringBuilder();
        int right = s.length() - 1;

        while (right >= 0) {
            // 先吃掉尾随空格与单词之间的多余空格。
            while (right >= 0 && s.charAt(right) == ' ') {
                right--;
            }

            if (right < 0) {
                break;
            }

            int left = right;

            while (left >= 0 && s.charAt(left) != ' ') {
                left--;
            }

            // 缓冲区非空才补分隔空格,首尾自然不会多出空格。
            if (ans.length() > 0) {
                ans.append(' ');
            }

            // 左指针已经越过词首,追加半开区间恢复单词原顺序
            ans.append(s, left + 1, right + 1);
            right = left;
        }

        return ans.toString();
    }
}
func reverseWords(s string) string {
    ans := make([]byte, 0, len(s))
    right := len(s) - 1
    for right >= 0 {
        // 先吃掉尾随空格与单词之间的多余空格。
        for right >= 0 && s[right] == ' ' {
            right--
        }
        if right < 0 {
            break
        }

        left := right
        for left >= 0 && s[left] != ' ' {
            left--
        }

        // 缓冲区非空才补分隔空格,首尾自然不会多出空格。
        if len(ans) > 0 {
            ans = append(ans, ' ')
        }
        // 左指针已经越过词首,追加半开区间恢复单词原顺序
        ans = append(ans, s[left+1:right+1]...)
        right = left
    }
    return string(ans)
}

复杂度分析

  • 时间复杂度:$O(n)$。指针只向左移动,每个字符被检查至多常数次;所有单词追加的总长度也不超过原串长度。
  • 空间复杂度:$O(n)$。结果缓冲区及最终字符串需要线性空间,扫描指针只占常数空间。

关键点总结

[!green]

  • 从右向左决定单词顺序,按原方向复制完整单词片段。
  • left 已经越过词首,right 仍在词尾,提取区间是 [left+1, right+1)。
  • 跳过原串空格、按需插入一个空格,分别负责识别单词和规范输出间隔。

易错点总结

[!yellow]

  • 直接反转所有字符,会连单词内部和标点位置一起反转。
  • 必须先检查指针非负,再访问字符;空串、全空格以及没有前导空格的首个单词都可能使指针到达 -1。
  • 把提取区间写成 [left, right),会包含单词前的空格并漏掉词尾字符。
  • 每取一个单词就无条件追加空格,会产生首尾多余空格;应只在已有结果和新单词之间追加。

相似题目

题目 难度 关联与区别
186. 反转字符串中的单词 II 中等 单词顺序翻转目标相同,原题以可修改字符数组输入,要求原地完成。
58. 最后一个单词的长度 简单 同样需要先处理空格并定位单词边界,原题只关心最后一个词的长度。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2022/92008641
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!