反转字符串中的单词

字符串 双指针

题目描述

给定一个字符串 s,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

要求:

  • 返回一个翻转后的字符串,单词之间只用一个空格隔开。
  • 输入字符串 s 中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,不应包含任何额外的空格。

思路分析

处理字符串翻转主要有两种常见的方式:

  1. 借助内置方法(分割+反转+拼接): 利用语言原生的字符串处理函数,首先去除首尾空格,再按空格把字符串拆分成由单词组成的数组,接着反转这个数组,最后再把数组用单一空格重新拼接。这种方式代码最简短、开发效率最高。

  2. 纯享版:双指针倒序遍历提取: 不依赖高级 API(如 split、正则等),直接从字符串末尾开始倒序遍历字符串自身。利用双指针(leftright)框定每个单词的边界,然后将其依次截取下来存放在新数组里,最后统一拼接返回结果。这对于理解指针移动和考察原生底层逻辑非常有帮助。

算法步骤 (双指针解法)

  1. 先将字符串去除首部和尾部的空格(或者遍历时忽略它们)。
  2. 从原字符串尾部向头部遍历,初始时两个指针 leftright 都位于结尾。
  3. 当遇到非空格字符时,left 持续向左移动,直至遇到第一个空格。这代表已经找到了一个单词的左边界。
  4. 将子串 [left + 1, right] 截取出来存入结果中。
  5. 接着 left 继续向左移动,跳过所有的相邻空格,直到遇到下一个单词的尾部,将 right 更新到此时 left 的位置。
  6. 不断重复上述两步处理,直到遍历完所有字符,最后把收集好的单词拼接起来。

示例代码

内置方法(切分、反转)
双指针(倒序遍历)
/**
 * 翻转字符串中的单词顺序。
 * 通过内置的字符串和数组方法,将字符串按空格拆解、反转数组后再重新拼装。
 * 
 * @param s 需要处理的原始字符串,其中可能包含多余的前后空格或单词间的连续多个空格
 * @returns 翻转单词顺序后得到的新字符串,单词之间仅用单个空格隔开,且首尾没有冗余空格
 */
export function reverseWords(s: string): string {
    // 1. s.trim():第一步清理数据,去除原字符串开头和结尾的空白字符。
    // 2. split(/\s+/):根据正则表达式 \s+(匹配一个或多个连续的空白字符)将字符串直接切割为“单词”数组。
    const arr = s.trim().split(/\s+/);

    // 3. arr.reverse():把刚刚切分好的单词数组掉转顺序,让最后一个单词跑到最前面。
    // 4. join(' '):最后用一个干净的空格作为胶水,把这些掉转顺序的单词重新粘合成一句话。
    return arr.reverse().join(' ');
}

复杂度分析

  • 时间复杂度O(n),其中 n 是字符串的长度。原生方法需要扫描拆分并反转;双指针解法从后往前遍历字符串一次。无论是直接切割还是逐字符寻找单词,处理代价都正比于字符串尺寸。
  • 空间复杂度O(n)。JavaScript 中字符串是不可变的类型,所以对于双指针解法我们也需要借助一个数组来接收每个新截取出的单词以重组结果,数组以及最终拼接成的字符串都会消耗 O(n) 的额外空间。

示例演示

s = " hello world " 为例,双指针处理过程如下:

  1. 初始剔除多余部分(如果有,或者跳过首尾空格)可以先将它当成 "hello world" 处理。
  2. 初始时,rightleft 都在末尾字母 'd' (索引 10) 处。
  3. 循环倒序寻找:left 一路向左,从 'd''l''r''o''w' 一直减到 ' '(索引 5)。此时,就确定了一个单词。
  4. 我们把子串(从 left + 1 为起始索引 6 一直到 right 为 10)即 "world" 保存到结果集合中。
  5. 然后继续移动:left 跳过当前的空格,停在字符 'o' (索引 4) 上。这时候把 right 对齐到 left,并重新开始寻找下一个单词的首字母。
  6. left 再度探索直到索引 -1,由于越界跳出寻找单词内部字母的循环。把 left + 1 (索引 0) 到 right (索引 4) 的单词 "hello" 追加存入。
  7. 此时结果集合中已经保存了 ["world", "hello"]
  8. 最终合并它们为 "world hello"