LeetCode-Go 位运算实战:190 题 Reverse Bits(颠倒二进制位)的 Go 解法与测试验证
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
本篇基于 LeetCode-Go 仓库中 190. Reverse Bits 题解文档 展开:在完整继承原题描述、示例与解题思路的基础上,结合仓库中 8 行核心源码 与 配套测试用例,逐行拆解“移位 + 掩码”的位反转实现。读完后你能掌握 Go 中uint32位运算的标准写法、如何验证 32 位全反转的正确性,以及该仓库如何用测试与覆盖率脚本保证题解可复现。
题目与需求:颠倒 32 位无符号整数的二进制位
仓库文档中对题面的原始描述是:颠倒给定的 32 位无符号整数的二进制位(Reverse bits of a given 32 bits unsigned integer)。两个官方示例如下:
示例 1:
输入: 00000010100101000001111010011100 输出: 00111001011110000010100101000000 解释: 输入二进制串 00000010100101000001111010011100 表示无符号整数 43261596, 返回 964176192,其二进制表示为 00111001011110000010100101000000。示例 2:
输入: 11111111111111111111111111111101 输出: 10111111111111111111111111111111 解释: 输入二进制串表示无符号整数 4294967293,返回 3221225471 (即 10111111111111111111111111111111)。文档同时给出了一条重要的语言相关说明:在某些语言(如 Java)中没有无符号整数类型,输入和输出会以有符号整数给出,但这不应影响实现——无论整数是有符号还是无符号,其内部的二进制表示形式都是相同的。以 Java 为例,编译器使用补码(two's complement)表示有符号整数,因此示例 2 的输入4294967293作为有符号整数是-3,输出3221225471作为有符号整数是-1073741825。这一点在后面“Go 视角”一节会再展开。
核心思路:循环 32 次,“取最低位、追加到 res”
原解题思路文档只给了两句关键描述,但已经点破了全部要点:
- 简单题,要求反转 32 位的二进制位;
- 把
num不断右移,消灭右边最低位的 1,将这个 1 给res,res不断左移即可实现反转。
把这个思路展开,每一步做三件事:
- 提取
num的当前最低位:num & 1,结果只可能是 0 或 1; - 把
res整体左移一位:res << 1,为最低位腾出空位; - 把刚提取的位放入
res的最低位:(res << 1) | (num & 1); - 同步右移
num:num >>= 1,让下一轮的“最低位”变成原来的次低位。
循环恰好 32 次后,num原本的最低位已经移动到res的最高位,num的最高位落在res的最低位——位序完全颠倒。用示例 1 走一遍:num = 43261596(00000010100101000001111010011100),第 1 次循环取出最低位0追加给res;第 2 次取出次低位0……直到第 32 次把最高位区的1全部搬到res低位,最终得到964176192(00111001011110000010100101000000)。
仓库中的 Go 实现:仅 8 行的完整源码
仓库中该题的解法位于 190. Reverse Bits.go,全文如下(含 package 声明共 10 行):
package leetcode func reverseBits(num uint32) uint32 { var res uint32 for i := 0; i < 32; i++ { res = res<<1 | num&1 num >>= 1 } return res }逐行分析:
num uint32是刻意为之。Go 原生提供 8 种整数类型(uint8~uint64),uint32就是“32 位无符号整数”本身,类型系统直接对应题目语义,无需像 Java 那样用int模拟无符号;var res uint32:Go 中声明的uint32零值即为0,无需初始化;for i := 0; i < 32; i++:固定 32 次,与输入数值大小无关——这也保证了边界值(如0或0xFFFFFFFF)同样处理 32 位,不会因前导 0 提前结束;res = res<<1 | num&1:一行完成“左移腾位 + 按位或追加”两个动作。注意|与&的 Go 运算符优先级:&高于|,所以该表达式等价于res = res<<1 | (num&1),源码中省略括号是安全的;num >>= 1:对无符号类型执行的是逻辑右移,高位补 0,不会像 Java 的>>对int做符号扩展,这正是“无论有符号无符号,实现不受影响”在 Go 中的自然体现。
整个函数没有任何分支与额外内存分配,时间上执行常数 32 次迭代(O(1) 时间复杂度),空间上只有两个寄存器级的 32 位变量(O(1) 空间复杂度)。
测试验证:用仓库的两组用例复现题目示例
同目录下的 190. Reverse Bits_test.go 采用仓库统一的题解测试模式:定义question190结构体,把“参数”与“期望答案”成对组织,直接对应题目中的两个示例:
func Test_Problem190(t *testing.T) { qs := []question190{ { para190{43261596}, ans190{964176192}, }, { para190{4294967293}, ans190{3221225471}, }, } fmt.Printf("------------------------Leetcode Problem 190------------------------\n") for _, q := range qs { _, p := q.ans190, q.para190 input := strconv.FormatUint(uint64(p.one), 2) // 32位无符号整数转换为二进制字符串 input = fmt.Sprintf("%0*v", 32, input) // 格式化输出32位,保留前置0 output := reverseBits(p.one) outputBin := strconv.FormatUint(uint64(output), 2) outputBin = fmt.Sprintf("%0*v", 32, outputBin) fmt.Printf("【input】:%v 【output】:%v (%v)\n", input, output, outputBin) } fmt.Printf("\n\n\n") }两个值得关注的工程细节:
%0*v宽度格式化:fmt.Sprintf("%0*v", 32, input)强制输出 32 位并保留前置 0。这一步至关重要——strconv.FormatUint转出的二进制串会丢弃前导 0(例如示例 1 输入会丢 6 个 0),不做补齐就无法与题目中“32 位串逐位对照”的验证方式对齐;- 数值断言 + 二进制串打印双轨验证:测试用例以数值
43261596 → 964176192、4294967293 → 3221225471作为正确答案锚点,同时把输入/输出都打印成 32 位二进制串,运行go test -v ./leetcode/0190.Reverse-Bits/时即可肉眼核对位序是否真正颠倒,而不是仅仅数值相等。
测试中的两个参数选取也覆盖了典型边界:43261596是高位大量为 0 的普通值,4294967293(即0xFFFFFFFD)是贴近uint32最大值4294967295的高位全 1 值,恰好对应题目 Note 中“有符号语言下即 -3”的极端场景。
仓库级的验证方式见 gotest.sh,其核心命令为:
go test -covermode=atomic -coverprofile=coverage.txt ./leetcode/...该脚本对./leetcode/...所有题解包一次性生成合法的覆盖率 profile(注释中说明:Go 1.10+ 支持对多个包一次性-coverprofile,可避免旧式“逐包 cat 追加”导致的重复mode: atomic头被 Codecov 误判为 0% 覆盖率),覆盖率结果落地在仓库根的 coverage.txt。也就是说,本篇展示的题解是被仓库 CI 全量回归覆盖的,而非孤立示例。
Go 视角下的“无符号整数”说明:为什么 Java 的注意事项在 Go 中自动消失
原题 Note 提醒:Java 没有无符号整数类型,输入输出会以int给出,例如示例 2 输入是-3、输出是-1073741825,但由于补码表示下位模式相同,实现不受影响。
对照仓库实现可以看到 Go 的处理方式更直接:
- 函数签名直接写
uint32,4294967293在 Go 里是合法的uint32常量(0xFFFFFFFD),不存在“超出int范围要转成 -3”的问题; - 源码中唯一的位运算链(
<<、&、|、>>)全部作用于uint32,>>对无符号类型是逻辑右移,行为确定; - 测试文件中把
uint32提升为uint64再交给strconv.FormatUint(_, 2),是为了得到一个无符号的 64 位容器来打印二进制串,这也是 Go 中“安全打印无符号二进制”的惯用手法。
因此,题目中那段“语言无关性”说明对 Go 解法的实际含义是:你不需要写任何兼容代码,uint32语义与题目完全一致;而算法本身(32 次移位取位)在 C、Java(配>>>与& 0xFFFFFFFFL技巧)中同样成立。
延伸:位反转背后的通用位技巧
这道题属于仓库主 README.md 中标记为“✅ 已完成”的 Bit Manipulation(位运算)专题。README 的 Bit Manipulation 小节 汇总了本仓库位运算题解常用的技巧,与 190 题直接相关的是:
Get the n-th bit of x (0 or 1): (x >> n) & 1 // 190 题的 num&1 就是 n=0 的特例 x & 1 == 1 测试 x 是否为奇数(X & 1 == 1) x &= (x - 1) 清除最低位的 1(LSB) x & -x 孤立最低位的 1190 题的num & 1正是“(x >> 0) & 1”的特例;而“循环右移 + 取位”的骨架也可以迁移到其他位操作题(如仓库中的 191. Number of 1 Bits、461. Hamming Distance 等同样依赖(x >> n) & 1与x &= x-1技巧)。若要在面试中进一步压缩 190 题的常数,常见优化是“分块反转”(按 8/16 位分组查表或分步& 掩码再拼接),但从当前仓库源码结构看,作者选择了最直白的 32 次循环写法——32 次恒定迭代本身就是 O(1),可读性优先,这也是该仓库“严格遵循 Google Golang Style Guide”风格的体现。
小结
| 要素 | 内容 | 依据 |
|---|---|---|
| 算法 | 循环 32 次:res = res<<1 \| num&1; num >>= 1 | 190. Reverse Bits.go |
| 复杂度 | O(1) 时间(恒定 32 次迭代)、O(1) 空间 | 源码循环上界为常量 32 |
| 测试锚点 | 43261596 → 964176192、4294967293 → 3221225471,均打印 32 位二进制串核对 | 190. Reverse Bits_test.go |
| 验证方式 | go test -v ./leetcode/0190.Reverse-Bits/;仓库级go test ./leetcode/...生成覆盖率 | gotest.sh、coverage.txt |
| 语言要点 | Go 的uint32原生匹配“32 位无符号整数”,逻辑右移无符号扩展顾虑 | 源码类型签名与题目 Note 对照 |
这篇题解的价值不仅在于一道 Easy 题的 8 行代码,更展示了 LeetCode-Go 仓库的完整题解范式:README 存题面与思路、Go 文件存最小可运行实现、_test.go用题目原始示例做回归锚点,三者配合并被覆盖率脚本统一校验。
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考