LC151
反转字符串中的单词
字符串双指针空格压缩整体反转 + 局部反转
给定字符串 s,反转其中单词的顺序,并删除首尾空格、把多个连续空格压缩成一个空格。不是简单 reverse 所有字符。
样例 1
输入:" hello world "
输出:"world hello"
样例 2
输入:"the sky is blue"
输出:"blue is sky the"
当前用例
" hello world "
先压缩空格,再整体反转 + 逐词反转
最终答案
"world hello"
复杂度
时间 O(n)
空间 O(n) 工程版 · O(1) follow-up 额外空间
两个测试用例 · 多空格 vs 干净输入
题目压缩空格整体反转逐词反转结论
题目Step 1/8
Step 1 · 题目输入
当前发生了什么
Step 1 · 题目输入
给定原始字符串 " hello world "。注意首尾有多余空格,中间 "hello" 和 "world" 之间也有连续空格。目标不是反转所有字符,而是反转单词顺序,并把空格规范成「无首尾空格、单词间仅一个空格」。
机器状态
- 阶段
- 输入
- output
- " hello world "
为什么正确
LC151 考的是词序反转 + 空格规范化,不是简单 reverse 整串。
面试该怎么说 · 工程写法:使用 strings.Fields(s) 得到单词数组,再双指针反转数组,最后 strings.Join(words, " ")。时间 O(n),空间 O(n)。
字符串可视化 · 输入
先压缩空格,再整体反转 + 逐词反转
␣
0␣
1h
2e
3l
4l
5o
6␣
7␣
8␣
9w
10o
11r
12l
13d
14␣
15␣
16当前字符串
"␣␣hello␣␣␣world␣␣"
多余/首尾空格fastslow当前单词
执行轨迹
- raw = " hello world "
- 目标 → "world hello"
Go 代码 · 工程可读版
题目版本 A · 工程可读版
1// 版本 A · 工程可读版 — strings.Fields + 双指针反转数组2func reverseWords(s string) string {3 words := strings.Fields(s) // 自动 trim + 过滤空串4 for l, r := 0, len(words)-1; l < r; l, r = l+1, r-1 {5 words[l], words[r] = words[r], words[l]6 }7 return strings.Join(words, " ")8}
版本 B · follow-up 原地 O(1)
10// 版本 B · follow-up — []byte 原地:压缩空格 → 整体反转 → 逐词反转11func reverseWordsFollowUp(s string) string {12 b := []byte(s)13 n := cleanSpaces(b) // fast/slow 压缩空格14 reverseRange(b, 0, n-1) // 整体反转15 reverseEachWord(b, n) // wordStart/wordEnd 逐词反转16 return string(b[:n])17}1819func cleanSpaces(b []byte) int {20 slow, needSpace := 0, false21 for fast := 0; fast < len(b); fast++ {22 if b[fast] != ' ' {23 if needSpace {24 b[slow] = ' '; slow++; needSpace = false25 }26 b[slow] = b[fast]; slow++27 } else if slow > 0 {28 needSpace = true // 单词之间才写入一个空格29 }30 }31 return slow32}3334func reverseRange(b []byte, left, right int) {35 for left < right {36 b[left], b[right] = b[right], b[left]37 left++; right--38 }39}4041func reverseEachWord(b []byte, n int) {42 start := 043 for i := 0; i <= n; i++ {44 if i == n || b[i] == ' ' {45 reverseRange(b, start, i-1) // 反转 [wordStart, wordEnd]46 start = i + 147 }48 }49}
不变量
- a. 压缩空格后,字符串中没有首尾空格,单词之间只有一个空格。
- b. 整体反转会改变单词顺序。
- c. 逐词反转会恢复每个单词内部顺序。
- d. 两次反转组合后,得到「单词顺序反转、单词内部不变」的结果。
8 步剧本 · Step 1/8
为什么「整体反转 + 逐词反转」能得到答案?
压缩空格保证输出格式合法。整体反转把最后一个单词移动到最前面。逐词反转只修复每个单词内部字符顺序,不改变单词之间的相对位置。因此最终结果满足:单词顺序反转,单词内部顺序不变,空格格式合法。
"hello world"
→ 整体反转 → "dlrow olleh"(词序反 + 词内反)
→ 逐词反转 → "world hello"(词内恢复,词序保持)
面试怎么答
工程写法
工程写法:使用 strings.Fields(s) 得到单词数组,再双指针反转数组,最后 strings.Join(words, " ")。时间 O(n),空间 O(n)。
follow-up 写法
follow-up 写法:如果字符串是可变字符数组(Go 里转 []byte),可以先原地压缩空格,再整体反转,再逐词反转。时间 O(n),额外空间 O(1)。
常见误区
- ✗ 只 reverse 整个字符串,忘记逐词反转 —— 会得到字符全反而不是词序反。
- ✗ 忘记处理首尾空格 —— 输出可能带多余空格。
- ✗ 忘记把多个连续空格压缩成一个 —— 输出格式不合法。
- ✗ 在 Go 里说「原地修改 string」,但 Go string 不可变,应转 []byte 或 []rune 讨论。
- ✗ 直接 strings.Split(s, " ") 后没有过滤空字符串 —— 会产生大量 "" 元素。