news 2026/9/10 17:59:21

LeetCode 477 题解:用 Go 按位统计 Total Hamming Distance,O(n) 一行公式算完所有数对

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 477 题解:用 Go 按位统计 Total Hamming Distance,O(n) 一行公式算完所有数对

LeetCode 477 题解:用 Go 按位统计 Total Hamming Distance,O(n) 一行公式算完所有数对

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

LeetCode 477(Total Hamming Distance)要求计算一个数组中所有数对汉明距离的总和。本文以 LeetCode-Go 仓库中该题的官方 Go 实现为主线,讲解"按位拆分 + 组合计数"的 O(32n) 解法,推导出k * (n - k)的核心公式,并对照仓库中注释为"暴力解法超时!"的 O(n²) 写法,分析为什么必须绕开两两枚举。读完本文,你将掌握汉明距离类题目的通用思考路径,并能直接复用仓库内的可运行源码与测试用例。

题目描述

两个整数之间的汉明距离定义为:这两个数字的二进制表示中,对应位不同的位置数量。

本题的任务是:给定一个整数数组,计算其中任意两个数之间汉明距离的总和

题目原文见 leetcode/0477.Total-Hamming-Distance/README.md。

示例

Input: 4, 14, 2 Output: 6

计算过程如下(只展示相关的四位二进制):

  • 4 的二进制:0100
  • 14 的二进制:1110
  • 2 的二进制:0010

两两之间的汉明距离:

HammingDistance(4, 14) = 2 HammingDistance(4, 2) = 2 HammingDistance(14, 2) = 2

总和:2 + 2 + 2 = 6

约束条件

  1. 数组中的元素取值范围为010^9
  2. 数组长度不超过10^4

10^9 < 2^30,再加上符号位,用 32 位二进制即可完整表示所有元素,这正是下文算法按 32 位扫描的依据。

前置知识:单对数字的汉明距离

在进入本题前,先回顾单对数字的汉明距离。仓库中 0461.Hamming-Distance 一题给出了最直接的思路:对xy做异或,异或结果的二进制中,为1的位就是两数对应位不同的位置,因此数出异或结果里1的个数即可。

LeetCode-Go 仓库在本题的源码中同样保留了这一工具函数,用于暴力解法,见 477. Total Hamming Distance.go:

func hammingDistance(x int, y int) int { distance := 0 for xor := x ^ y; xor != 0; xor &= (xor - 1) { distance++ } return distance }

这里用到经典的位操作技巧xor &= (xor - 1):每执行一次,就把xor最低位的1清零(该技巧与 Bit Manipulation 专题 中总结的X &= (X - 1) 将最低位(LSB)的 1 清零完全对应),所以循环次数就是1的个数,即两数的汉明距离。

核心思路:按位统计,而不是按数对统计

如果直接套用上面的单对函数去枚举所有数对,复杂度是 O(n²),在n = 10^4时约需要计算10^8次数对,必然超时。仓库源码中的暴力写法totalHammingDistance1就注释着"暴力解法超时!"。

正确的思路是把"数对维度"的统计,拆解为"位维度"的统计,这正是原 README 解题思路的核心:

把数组中的每个元素 32 位的二进制位依次扫一遍,当扫到某一位的时候,有k个元素在这个位上的值是 1,n - k个元素在这个位上的值是 0,那么在这一位上所有两两元素的海明距离是k * (n - k)。当把 32 位全部扫完以后,累加出来的海明距离就是所有两两元素的海明距离。

为什么某一位的贡献是 k × (n − k)

这一结论的推导非常直观:

  • 两个数在某一位上的汉明距离贡献为 1,当且仅当这两个数在这一位上一个是1、另一个是0
  • 假设这一位上值为1的元素有k个,值为0的元素有n - k个;
  • 那么"一个取 1、一个取 0"的数对数量正好是k × (n - k)
  • 因此这一位对总和的贡献就是k × (n - k)

由于每一位的贡献只取决于该位上1的个数k,与具体是哪些数对无关,不同位之间的贡献又可以独立累加,最终答案就是 32 位贡献之和。整个过程只需扫描32 × n次,时间复杂度 O(32n) ≈ O(n)。

Go 源码实现解读

仓库给出的最优解实现非常精简,完整代码如下(477. Total Hamming Distance.go):

func totalHammingDistance(nums []int) int { total, n := 0, len(nums) for i := 0; i < 32; i++ { bitCount := 0 for j := 0; j < n; j++ { bitCount += (nums[j] >> uint(i)) & 1 } total += bitCount * (n - bitCount) } return total }

逐行拆解:

  1. total, n := 0, len(nums)total累加每一位的贡献;n为数组长度,用于计算n - bitCount
  2. 外层for i := 0; i < 32; i++:遍历 32 个二进制位。取 32 位是因为元素最大值10^9 < 2^30,32 位足够覆盖(含符号位),多出的高位全为 0,bitCount恒为 0,不影响结果。
  3. 内层for j := 0; j < n; j++:统计第i位上值为1的元素个数。
    • nums[j] >> uint(i):把第i位移到最低位;
    • & 1:取出该位,得到 0 或 1;
    • bitCount += ...:累加得到k
  4. total += bitCount * (n - bitCount):套用公式,累加该位对总汉明距离的贡献。

值得注意的细节:源码中对外层位索引i做了uint(i)转换,这是因为 Go 语言中移位操作的右操作数要求是无符号整数(或能被隐式转换为无符号整数),这也是 Go 位运算题目中常见的写法。

复杂度分析

  • 时间复杂度:O(32n) = O(n),只需扫描 32 次数组,与数对数量无关;
  • 空间复杂度:O(1),仅使用totalbitCount等常数个变量,不依赖输入规模。

对比暴力解法 O(n²) 的时间复杂度,当n = 10^4时两者相差约 3 个数量级,这就是本题必须按位统计的根本原因。

暴力解法对照:为什么它超时

仓库源码中保留了暴力解作为对照,明确标注"暴力解法超时!":

// 暴力解法超时! func totalHammingDistance1(nums []int) int { res := 0 for i := 0; i < len(nums); i++ { for j := i + 1; j < len(nums); j++ { res += hammingDistance(nums[i], nums[j]) } } return res }

它枚举所有下标对(i, j)i < j),对每对数调用hammingDistance逐位统计差异,总复杂度为:

C(n, 2) × 位数 ≈ n² / 2 × 32

n = 10^4时约为1.6 × 10^9次位操作,在 LeetCode 的时间限制下必然超时。这两版代码同处一个文件中,恰好构成"错误思路 vs 正确思路"的直观对比:暴力版逻辑正确但不可行,按位统计版才是本题的标准解。

测试用例与运行验证

仓库为该题提供了完整的单元测试,见 477. Total Hamming Distance_test.go。测试框架沿用 LeetCode-Go 仓库统一的"参数 + 期望答案"结构:

type para477 struct { one []int } type ans477 struct { one int }

测试主体Test_Problem477使用的用例与题目示例一致:

qs := []question477{ { para477{[]int{4, 14, 2}}, ans477{6}, }, }

即输入[4, 14, 2],期望输出6。测试同时调用totalHammingDistancetotalHammingDistance1两个版本,既验证了最优解的正确性,也验证了暴力解在逻辑上无误(只是性能不达标)。

运行测试的命令如下(仓库根目录执行):

go test ./leetcode/0477.Total-Hamming-Distance/...

LeetCode-Go 仓库对全部题解执行统一测试并生成覆盖率文件,相关脚本见 gotest.sh(使用-covermode=atomic-coverprofile=coverage.txt一次性覆盖./leetcode/...全部包)。

解题要点小结

  • 变换统计维度:把"数对之间逐位比较"转换为"每一位上统计 1 的个数",是本题破题的关键;
  • 组合计数公式:某一位上k个 1、n - k个 0 时,该位贡献k × (n - k)
  • 扫描范围:元素最大10^9 < 2^30,因此扫描 32 位即可覆盖全部有效位;
  • Go 移位细节:移位操作数需为无符号类型,源码中用uint(i)完成转换;
  • 复杂度优势:O(32n) 时间、O(1) 空间,轻松应对n = 10^4的输入规模。

延伸阅读

  • 汉明距离的基础版本(单对数对):461. Hamming Distance 题解,掌握X &= (X - 1)与异或计数技巧;
  • 仓库的 Bit Manipulation 位运算专题 系统性总结了异或特性、特殊 Mask 构造以及各类经典位操作,本题使用的(x >> n) & 1取位技巧即出自该专题;
  • 仓库根 README.md 中 Bit Manipulation 分类已全部完成(✅),可按编号检索更多位运算题目进行配套练习。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 17:59:19

Flink实时日志分析系统架构与优化实践

1. 项目概述&#xff1a;当Flink遇见日志分析日志数据就像企业的神经系统&#xff0c;每时每刻都在记录着系统的运行状态。但传统批处理式的日志分析存在明显滞后性&#xff0c;往往在问题发生数小时后才能发现异常。我们团队去年在金融风控场景中就吃过这样的亏——等批量日志…

作者头像 李华
网站建设 2026/9/10 17:55:43

Ionic Range组件详解:从基础使用到高级定制

1. Ionic Range组件基础解析Ionic框架中的Range组件是一个功能强大的滑动输入控件&#xff0c;它允许用户通过拖动滑块在指定范围内选择数值。这个组件在移动端和Web端都能提供一致的用户体验&#xff0c;特别适合需要精确调节参数的场景。Range组件最典型的应用场景包括&#…

作者头像 李华
网站建设 2026/9/10 17:49:59

孤岛式直流微电网分层控制与Matlab仿真实践

1. 项目概述这个项目实现了一个具有灵活结构的孤岛式直流微电网分层控制系统&#xff0c;基于IEEE 16节点测试模型&#xff0c;使用Matlab进行仿真实现。孤岛式直流微电网是指不接入主电网、独立运行的直流微电网系统&#xff0c;其分层控制架构是实现系统稳定运行的关键技术。…

作者头像 李华
网站建设 2026/9/10 17:48:52

Solidworks导出URDF文件过大的优化技巧

1. 问题背景&#xff1a;为什么Solidworks导出的URDF文件过大&#xff1f;在机器人仿真领域&#xff0c;Solidworks作为主流的三维建模软件&#xff0c;常被用于机械结构设计。当我们需要将设计好的机器人模型导入MuJoCo等物理引擎进行运动学/动力学仿真时&#xff0c;通常需要…

作者头像 李华
网站建设 2026/9/10 17:47:28

信息熵与霍夫曼编码:MATLAB实现与工程实践

1. 信息熵与无损编码的理论基础 信息熵是信息论中最核心的概念之一&#xff0c;它量化了信源的不确定性。对于离散信源X&#xff0c;其信息熵H(X)定义为&#xff1a; H(X) -Σ p(x) log₂ p(x) 这个公式揭示了几个关键特性&#xff1a; 当某个事件x的概率p(x)趋近于1时&…

作者头像 李华