数组一长,这题就别想着两层 for 了。n=10^5 的时候,你真去枚举两两组合,机器风扇先响,提交结果后响的就是你自己。 “汉明距离总和”这种题,我第一眼看的不是异或怎么写,而是:有没有必要真的把每一对都算出来。
先把题意掰直。给你一个整数数组,任意两个数之间都可以算一个汉明距离,意思就是这两个数转成二进制以后,有多少位不一样。比如 4 和 14:
4 = 010014 = 1110
不一样的位有 2 个,所以汉明距离是 2。
如果题目要的是“总和”,很多人下意识就会这么写:
publicinttotalHammingDistance(int[] nums){int sum = 0;for (int i = 0; i < nums.length; i++) {for (int j = i + 1; j < nums.length; j++) { sum += Integer.bitCount(nums[i] ^ nums[j]); } }return sum;}
写法没错,甚至还挺顺。问题是复杂度 O(n^2),数据一大基本就没戏了。
这题真正该看的地方,在“按位统计”。
别老盯着两个数怎么比。换个角度,去看某一位上到底有多少个 0,多少个 1。 因为某一位只要一个是 0,一个是 1,这一对数在这一位上就会贡献 1 次汉明距离。
所以第 k 位的贡献,其实就是:
这一位上 1 的个数 * 这一位上 0 的个数
这个式子挺值钱。它的意思是,不用枚举配对,直接把所有“能凑成不同位的组合数”一次算出来。
举个很小的例子,数组是:
[4, 14, 2]
二进制大概看成:
4 = 010014 = 11102 = 0010
看第 1 位,有两个 1,一个 0,这一位贡献就是 2 * 1 = 2。 看第 2 位,也是一样,再贡献 2。 其他位贡献是 0。 总和就是 4。
代码写出来反而不复杂:
publicclassSolution{publicinttotalHammingDistance(int[] nums){int n = nums.length;int ans = 0;// int 通常按 32 位来处理for (int bit = 0; bit < 32; bit++) {int ones = 0;for (int num : nums) { ones += (num >>> bit) & 1; } ans += ones * (n - ones); }return ans; }}
这里有两个细节我一般会顺手注意一下。
第一个,移位我用的是 >>>,不是 >>。 虽然很多题的数据范围都是非负数,用 >> 也能过,但我写这种位运算时不太爱赌输入条件,>>> 是无符号右移,语义更稳一点。
第二个,为什么不会重复计算? 因为我们按位统计时,ones * (n - ones) 算出来的就是这一位上所有“一个取 1,一个取 0”的组合数,每一对只算一次,刚好对应题目要的配对贡献,不多不少。
复杂度也很舒服。外层固定 32 次,内层扫一遍数组,所以总复杂度是 O(32 * n),本质上就是 O(n)。空间复杂度 O(1)。
这种题有个很典型的误区: 看见“任意两个数”,就条件反射写两层循环。这个习惯在简单题里问题不大,一到统计型位运算题就容易吃亏。很多时候题目不是让你把过程老老实实模拟出来,而是逼你换个统计口径。
说到底,这题难的不是异或,也不是 bitCount,而是你能不能早点意识到:逐对计算是动作,逐位统计才是思路。