首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【LeetCode刷题日记】:151翻转字符串的单词(两种解法)

【LeetCode刷题日记】:151翻转字符串的单词(两种解法)

作者头像
北极的代码
发布2026-04-22 13:52:20
发布2026-04-22 13:52:20
820
举报

摘要:

本题要求翻转字符串中的单词顺序,并去除首尾及单词间多余空格。核心解法分三步:先去除多余空格(利用双指针和StringBuilder动态判断,确保单词间仅保留一个空格),再反转整个字符串(双指针交换字符),最后逐个反转每个单词(通过空格定位单词边界)。另一种解法是创建新字符数组,从后向前遍历原字符串,提取单词并依次放入新数组,天然实现顺序反转,最后去掉末尾多余空格。两种方法时间复杂度均为 O(n),前者空间 O(1),后者空间 O(n)。

题目背景:LeetCode151 翻转字符串的单词(中等)

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

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

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

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

示例 1:

代码语言:javascript
复制
输入:s = "the sky is blue"
输出:"blue is sky the"

示例 2:

代码语言:javascript
复制
输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。

示例 3:

代码语言:javascript
复制
输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

提示:

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

题目解析:

其实这道题目整体看上去是翻转,其实还有一个重要的逻辑就是去空,单词的开头不能有空格,单词与单词之间就是靠一个空格来分隔的,但是只有一个,如果有多个就要去除


这正是题目要求的

反转后的字符串中不能存在前导空格和尾随空格。 如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个

那我们来思考该怎么操作呢,首先就应当先把不需要的空格都去掉,否则这会影响我们之后的翻转操作,因此这个操作的优先级比较高。

那我们怎么去掉这些空格呢

首先是首尾,其实还比较简单,主要的就是单词与单词之间只能有一个空格,我们定义一个方法,之后调用去空格。我们进行判断首位,直接用while循环,很轻松的就去掉两个空格了

然后我们利用StringBuilder创建一个可变的字符串,因为java的字符串本身是不可变的。这样我们就能对字符串进行增删改查。之后就是处理单词之间的空格了,

代码语言:javascript
复制
 char c = s.charAt(start);
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            start++;

情况

条件

说明

1

c != ' '

当前字符不是空格(是字母),直接加入

2

c == ' ' 且 sb 最后一个字符不是空格

当前是空格,但上一个加入的不是空格,说明这是单词间的分隔符,加入一个空格

这里的操作就是去掉多余的空格,保证只能有一个空格。

sb.charAt(sb.length() - 1) 不是只找一次最后的 'd',而是每处理一个字符时,动态查看 sb 当前已经构建好的内容的最后一个字符是什么,从而决定要不要添加新的空格。这是一个实时判断的过程。

举例演示

输入:" hello world "

去除首尾空格后

text

代码语言:javascript
复制
start → h e l l o   w o r l d ← end
        0 1 2 3 4 5 6 7 8 9 10 11
逐字符处理

start

c

sb当前内容

sb最后一个字符

条件判断

是否追加

sb新内容

0

'h'

""

(不存在)

c≠空格 ✅

"h"

1

'e'

"h"

'h'

c≠空格 ✅

"he"

2

'l'

"he"

'e'

c≠空格 ✅

"hel"

3

'l'

"hel"

'l'

c≠空格 ✅

"hell"

4

'o'

"hell"

'l'

c≠空格 ✅

"hello"

5

' '

"hello"

'o'

c==空格 且 sb最后一个字符='o'≠空格 ✅

"hello "

6

' '

"hello "

' '

c==空格 且 sb最后一个字符=' ' ❌

"hello " (不变)

7

'w'

"hello "

' '

c≠空格 ✅

"hello w"

8

'o'

"hello w"

'w'

c≠空格 ✅

"hello wo"

9

'r'

"hello wo"

'o'

c≠空格 ✅

"hello wor"

10

'l'

"hello wor"

'r'

c≠空格 ✅

"hello worl"

11

'd'

"hello worl"

'l'

c≠空格 ✅

"hello world"

代码语言:javascript
复制
 private StringBuilder removeSpace(String s) {
        // System.out.println("ReverseWords.removeSpace() called with: s = [" + s + "]");
        int start = 0;
        int end = s.length() - 1;
        while (s.charAt(start) == ' ') start++;
        while (s.charAt(end) == ' ') end--;
        StringBuilder sb = new StringBuilder();
        while (start <= end) {
            char c = s.charAt(start);
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            start++;
        }
        // System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
        return sb;
    }

去除空格之后,我们要做的就是翻转整个字符串了

利用双指针法:

逐步拆解

1. 循环条件 while (start < end)

只要左指针在右指针左边,就继续交换。当两个指针相遇或交错时,说明已经全部交换完毕。

2. 交换三部曲

代码语言:javascript
复制
java

char temp = sb.charAt(start);        // 步骤1:暂存左边的字符
sb.setCharAt(start, sb.charAt(end)); // 步骤2:把右边字符放到左边位置
sb.setCharAt(end, temp);             // 步骤3:把暂存的左边字符放到右边位置

3. 移动指针

代码语言:javascript
复制
java

start++;  // 左指针右移
end--;    // 右指针左移

之后就是单独的翻转单词了,把单词的顺序倒过来,这样就完成了我们要的翻转单词位置了

我们这里定义start end,是为了确定每个单词的首尾位置,这要确定之后,我们再调用第二步的翻转全部,也就是把这一个单词的顺序翻转正常了,每个单词的分界线是空格,通过空格来确定。


执行过程示例

假设当前 sb = "olleh world"(这是反转整个字符串后的结果,目标是变成 "hello world"

text

代码语言:javascript
复制
初始: o  l  l  e  h     w  o  r  l  d
      0  1  2  3  4  5  6  7  8  9  10
                      ↑
                   空格位置
第1轮循环(处理第一个单词 "olleh")

变量

说明

start

0

第一个单词从索引0开始

end

1

开始寻找单词结尾

内层 while

  • end=1, sb.charAt(1)='l' ≠ 空格 → end=2
  • end=2, sb.charAt(2)='l' ≠ 空格 → end=3
  • end=3, sb.charAt(3)='e' ≠ 空格 → end=4
  • end=4, sb.charAt(4)='h' ≠ 空格 → end=5
  • end=5, sb.charAt(5)=' ' == 空格 → 循环结束

找到单词范围:start=0, end=5(end指向空格)

代码语言:javascript
复制
java

reverseString(sb, 0, 4);  // 反转索引0~4
// "olleh" → "hello"
题目答案:
代码语言:javascript
复制
class Solution {
   /**
     * 不使用Java内置方法实现
     * <p>
     * 1.去除首尾以及中间多余空格
     * 2.反转整个字符串
     * 3.反转各个单词
     */
    public String reverseWords(String s) {
        // System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]");
        // 1.去除首尾以及中间多余空格
        StringBuilder sb = removeSpace(s);
        // 2.反转整个字符串
        reverseString(sb, 0, sb.length() - 1);
        // 3.反转各个单词
        reverseEachWord(sb);
        return sb.toString();
    }

    private StringBuilder removeSpace(String s) {
        // System.out.println("ReverseWords.removeSpace() called with: s = [" + s + "]");
        int start = 0;
        int end = s.length() - 1;
        while (s.charAt(start) == ' ') start++;
        while (s.charAt(end) == ' ') end--;
        StringBuilder sb = new StringBuilder();
        while (start <= end) {
            char c = s.charAt(start);
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            start++;
        }
        // System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
        return sb;
    }

    /**
     * 反转字符串指定区间[start, end]的字符
     */
    public void reverseString(StringBuilder sb, int start, int end) {
        // System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            start++;
            end--;
        }
        // System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
    }

    private void reverseEachWord(StringBuilder sb) {
        int start = 0;
        int end = 1;
        int n = sb.length();
        while (start < n) {
            while (end < n && sb.charAt(end) != ' ') {
                end++;
            }
            reverseString(sb, start, end - 1);
            start = end + 1;
            end = start + 1;
        }
    }
}

这里我们还有第二种写法,代码比较简单:

代码语言:javascript
复制
//解法二:创建新字符数组填充。时间复杂度O(n)
class Solution {
    public String reverseWords(String s) {
        //源字符数组
        char[] initialArr = s.toCharArray();
        //新字符数组
        char[] newArr = new char[initialArr.length+1];//下面循环添加"单词 ",最终末尾的空格不会返回
        int newArrPos = 0;
        //i来进行整体对源字符数组从后往前遍历
        int i = initialArr.length-1;
        while(i>=0){
            while(i>=0 && initialArr[i] == ' '){i--;}  //跳过空格
            //此时i位置是边界或!=空格,先记录当前索引,之后的while用来确定单词的首字母的位置
            int right = i;
            while(i>=0 && initialArr[i] != ' '){i--;} 
            //指定区间单词取出(由于i为首字母的前一位,所以这里+1,),取出的每组末尾都带有一个空格
            for (int j = i+1; j <= right; j++) {
                newArr[newArrPos++] = initialArr[j];
                if(j == right){
                    newArr[newArrPos++] = ' ';//空格
                }
            }
        }
        //若是原始字符串没有单词,直接返回空字符串;若是有单词,返回0-末尾空格索引前范围的字符数组(转成String返回)
        if(newArrPos == 0){
            return "";
        }else{
            return new String(newArr,0,newArrPos-1);
        }
    }
}

这就是典型的新开辟一个空间,把旧数组循环遍历到新数组,实现天然的翻转

创建新数组的时候,需要多一个 位置,用来放空格,遍历的时候新数组的指针是从0开始,原来数组的指针是从最后开始。

第1轮循环(提取 "world")

java

代码语言:javascript
复制
// 跳过空格
i=15: ' ' → i=14
i=14: ' ' → i=13
i=13: 'd' → 停止

right = i = 13

// 向左找单词左边界
i=13: 'd' ≠ ' ' → i=12
i=12: 'l' ≠ ' ' → i=11
i=11: 'r' ≠ ' ' → i=10
i=10: 'o' ≠ ' ' → i=9
i=9:  'w' ≠ ' ' → i=8
i=8:  ' ' == ' ' → 停止

// 单词范围 [i+1, right] = [9, 13]
// 复制 "world" + 空格
newArr: [w][o][r][l][d][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
        0  1  2  3  4  5
newArrPos = 6
第2轮循环(提取 "hello")

java

代码语言:javascript
复制
// i 当前是 8,指向空格,继续跳过空格
i=8: ' ' → i=7
i=7: ' ' → i=6
i=6: 'o' → 停止

right = i = 6

// 向左找单词左边界
i=6: 'o' ≠ ' ' → i=5
i=5: 'l' ≠ ' ' → i=4
i=4: 'l' ≠ ' ' → i=3
i=3: 'e' ≠ ' ' → i=2
i=2: 'h' ≠ ' ' → i=1
i=1: ' ' == ' ' → 停止

// 单词范围 [i+1, right] = [2, 6]
// 复制 "hello" + 空格
newArr: [w][o][r][l][d][ ][h][e][l][l][o][ ][ ][ ][ ][ ][ ]
        0  1  2  3  4  5  6  7  8  9  10 11
newArrPos = 12

设计

原因

newArr 长度 +1

每个单词后加空格,最后一个空格会被去掉

从后往前遍历

天然实现单词顺序反转

单词后加空格

统一处理,最后再去掉末尾空格

newArrPos-1

去掉最后多余的那个空格

优缺点分析

优点:

  • 思路清晰直观
  • 一次遍历完成,时间复杂度 O(n)
  • 不需要多次反转操作

缺点:

  • 需要额外 O(n) 空间
  • 代码稍长
对比两种解法

特性

解法一(原地反转)

解法二(新数组填充)

空间复杂度

O(1)

O(n)

代码复杂度

较高(需要多个辅助方法)

较低(一个方法搞定)

理解难度

较难

容易

适用场景

要求原地修改

无空间限制

结语:如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目背景:LeetCode151 翻转字符串的单词(中等)
    • 题目解析:
    • 举例演示
      • 去除首尾空格后
      • 逐字符处理
    • 去除空格之后,我们要做的就是翻转整个字符串了
      • 执行过程示例
    • 题目答案:
  • 这里我们还有第二种写法,代码比较简单:
    • 第1轮循环(提取 "world")
    • 第2轮循环(提取 "hello")
    • 优缺点分析
    • 对比两种解法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档