LeetCode-Go 题解精讲:151. Reverse Words in a String —— 用 `strings.Fields` 一行拆词、双指针翻转,优雅实现单词级反转
2026/9/13 17:00:51 网站建设 项目流程

LeetCode-Go 题解精讲:151. Reverse Words in a String —— 用strings.Fields一行拆词、双指针翻转,优雅实现单词级反转

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

导读

本篇围绕 LeetCode 第 151 题「反转字符串中的单词(Reverse Words in a String)」展开,以 LeetCode-Go 仓库中 0151.Reverse-Words-in-a-String 目录的官方题解为骨架,深入拆解题目约束、标准解法与 Go 语言实现。读完本文,你将掌握:如何借助 Go 标准库strings.Fields一次性完成「拆分单词 + 消除首尾与连续多余空格」,再配合双指针原地翻转单词切片、最后用strings.Join重建字符串的完整套路,并理解其时间与空间复杂度,以及为何这套写法天然满足题目对空格处理的所有要求。

题目:逐个翻转字符串中的每个单词

题目原链接:151. Reverse Words in a String(LeetCode 官方题面)

给定一个输入字符串,要求以单词为最小单位将其反转。核心约束如下:

  • 单词的定义:由非空格字符组成的连续序列(sequence of non-space characters),标点符号如!.与字母一同构成单词的一部分,不做单独处理;
  • 首尾空格:输入字符串可以包含前导(leading)或尾随(trailing)空格,但反转后的结果不能包含这些多余空格;
  • 单词间空格:如果两个单词之间存在多个连续空格,反转后需将单词间的空格缩减为单个空格

示例

输入输出说明
"the sky is blue""blue is sky the"无多余空格,仅需整体倒序
" hello world! ""world! hello"去掉首尾空格;world!!属于单词的一部分
"a good example""example good a"将单词间多个空格压缩为单个空格

进阶要求(Follow-up)

对于选择C 语言的读者,题目建议尝试使用O(1) 额外空间复杂度的**原地(in-place)**解法。Go 语言由于字符串不可变,本文给出的标准库解法基于strings.Fields+ 双指针,属于 O(n) 辅助空间的实现;若要在 O(1) 额外空间下原地完成,可参考 C 语言中「先整体反转字符串、再逐个反转每个单词」的两段式思路(下文会做原理对比,但仓库内的官方 Go 解法并不采用该路径)。

解题思路:拆词 → 翻转 → 拼接

依照题意,标准解法分三步走,与题目约束一一对应:

  1. 拆分(split):先把输入字符串按照空格(可连续、可在首尾)分隔成一个个独立单词——这一步同时天然解决了「压缩多余空格」和「剔除首尾空格」两个问题;
  2. 翻转(reverse):把单词序列整体前后翻转,达到"以单词为单位反转"的效果;
  3. 拼接(join):在每个翻转后的单词之间补上单个空格,重建最终字符串。

整体思路即:strings.Fields(拆)→ 双指针reverse(翻)→strings.Join(拼)。仓库文档(website/content.en/ChapterFour/0100~0199/0151.Reverse-Words-in-a-String.md)中对该思路有明确概述:"first split the string into individual words by spaces, then reverse the order of the words, and finally add spaces between each word."

Go 官方题解:拆词 → 翻转 → 拼接

仓库在 leetcode/0151.Reverse-Words-in-a-String/151. Reverse Words in a String.go 中给出了如下实现:

package leetcode import "strings" func reverseWords151(s string) string { ss := strings.Fields(s) reverse151(&ss, 0, len(ss)-1) return strings.Join(ss, " ") } func reverse151(m *[]string, i int, j int) { for i <= j { (*m)[i], (*m)[j] = (*m)[j], (*m)[i] i++ j-- } }

逐行拆解

  • strings.Fields(s):Go 标准库函数,以空白字符(空格、tab、换行等 Unicode 空白)为分隔符拆分字符串,并自动丢弃所有前导、尾随及连续多余的空格,返回单词切片。这一步以极简方式同时满足题目 Note 中的两条硬性约束——"reversed string should not contain leading or trailing spaces" 与 "reduce multiple spaces between two words to a single space";
  • reverse151(&ss, 0, len(ss)-1):对单词切片做双指针原地翻转i指向切片头、j指向切片尾,循环交换ss[i]ss[j],直到两指针相遇(i > j)。通过传切片指针,翻转直接作用于底层数组,无需额外分配;
  • strings.Join(ss, " "):用单个空格把翻转后的单词连接成最终字符串。

关键设计点:为什么传入指针*[]string

reverse151的形参是*[]string(切片指针)。这里需要说明:Go 的切片本身就是引用类型,若直接传值ss,函数内部对元素的修改(m[i], m[j] = m[j], m[i])同样会反映到调用方——因为元素读写作用于共享的底层数组。而传入指针的写法在语义上更明确地表达了"函数会修改这个切片的状态",属于显式、防御性的编码风格,也是本项目(LeetCode-Go)题解中常见的写法之一。

单测验证:三个官方示例全覆盖

仓库为本题配备了完整测试文件 leetcode/0151.Reverse-Words-in-a-String/151. Reverse Words in a String_test.go,采用本仓库统一的"表驱动(table-driven)+ 标准testing包"组织方式:

package leetcode import ( "fmt" "testing" ) type question151 struct { para151 ans151 } // para 是参数 // one 代表第一个参数 type para151 struct { one string } // ans 是答案 // one 代表第一个答案 type ans151 struct { one string } func Test_Problem151(t *testing.T) { qs := []question151{ { para151{"the sky is blue"}, ans151{"blue is sky the"}, }, { para151{" hello world! "}, ans151{"world! hello"}, }, { para151{"a good example"}, ans151{"example good a"}, }, } fmt.Printf("------------------------Leetcode Problem 151------------------------\n") for _, q := range qs { _, p := q.ans151, q.para151 fmt.Printf("【input】:%v 【output】:%v\n", p, reverseWords151(p.one)) } fmt.Printf("\n\n\n") }

测试覆盖了题目给出的三个标准示例,正好对应三种边界情形:

  1. "the sky is blue""blue is sky the":常规情况,无多余空格;
  2. " hello world! ""world! hello"首尾带空格,验证结果不含前导/尾随空格;
  3. "a good example""example good a"单词间多个空格,验证结果只保留单个空格。

每个用例均以para151(输入参数)/ans151(期望答案)的结构组织,再逐条调用reverseWords151(p.one)与期望结果比对。得益于strings.Fields对空白的分隔与清理语义,这三个用例无需任何特殊分支即可全部通过。

若要在本地运行该测试,可在仓库根目录执行(仓库 go.mod 声明模块github.com/halfrost/LeetCode-Go,Go 版本 1.19):

go test -v -run Test_Problem151 ./leetcode/0151.Reverse-Words-in-a-String/

复杂度与边界分析

时间复杂度:O(n)

  • strings.Fields(s)需线性扫描字符串一次,O(n);
  • 双指针reverse151len(ss)个单词各交换一次,O(len(ss)) ≤ O(n);
  • strings.Join同样线性拼接,O(n)。

总体时间复杂度O(n),其中 n 为输入字符串长度。

空间复杂度:O(n)

  • strings.Fields会为单词切片分配 O(n) 空间(单词总长度不超过原串长度,切片本身另需 O(单词数) 开销);
  • 翻转是原地交换,无额外分配;
  • strings.Join为结果字符串分配 O(n) 空间。

总体空间复杂度O(n)。这与题目对 C 语言用户的 O(1) 原地进阶要求不同——Go 的string不可变,标准库解法以 O(n) 空间换取极简实现,属于工程上的合理取舍。

边界情形覆盖

输入说明
"the sky is blue"常规多词输入
" hello world! "前导 + 尾随空格,!属于单词world!
"a good example"单词间连续多个空格
" "(纯空格串)strings.Fields返回空切片,翻转与Join均为空操作,最终返回""
""(空串)同上,返回""
"single"(单单词)无空格可拆,翻转后原样返回"single"

方案对比:标准库 vs 两段式原地翻转

题目进阶要求(C 语言方向)提示了另一条经典路径——两段式原地反转,与本文采用的标准库方案形成互补:

  1. 整体反转:先把整个字符串反转(包括空格),使单词顺序倒置;
  2. 局部反转:再扫描字符串,对每个单词内部做反转恢复其原始字母顺序;
  3. 清理空格:最后压缩多余空格并去除首尾空格。

该思路可用 O(1) 额外空间完成,但实现复杂度明显更高:需要自己处理单词边界扫描、空格压缩与滑动覆盖,代码量远大于本文的三行核心逻辑。作为对照,Go 标准库方案将"拆词 + 去多余空格 + 去首尾空格"这一最易出错的部分全部交给经过充分验证的strings.Fields完成,把"翻转单词顺序"这一唯一的核心逻辑留给业务代码,正确性与可读性显著占优。

两种方案本质互补:追求极简与可读性选标准库方案;追求 O(1) 额外空间(或需要迁移到 C 等可变字符串语言)时,两段式原地翻转是标准答案。

总结

LeetCode 151 是一道典型的"字符串 + 双指针"入门题,考察重点在于对空格边界条件的处理:

  • 单词= 非空格字符序列,标点随单词一起反转;
  • 首尾空格必须剔除、连续空格必须压缩为单个,这两条约束由strings.Fields一次性兜底;
  • 核心算法= 拆词 → 双指针翻转 → 单空格拼接,时间复杂度 O(n)。

仓库在 leetcode/0151.Reverse-Words-in-a-String 目录中同时提供了 题解实现、表驱动测试 与 题目说明 README,对应的英文题解页面位于 website/content.en/ChapterFour/0100~0199/0151.Reverse-Words-in-a-String.md,是复习该题与类似字符串处理题(如 345. Reverse Vowels、557. Reverse Words in a String III 等)的绝佳参考。

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询