某大厂员工吐槽:公司领导夜里10点发群消息,大家都是已读不回。哪知凌晨2点,领导在群里发飙:以后“已读不回”一律按旷工处理。
刚刷到这个帖,我第一反应就是,领导这班味儿已经冲出屏幕了。晚上10点往群里丢消息,大家已读不回,其实意思已经很明显了:哥几个下班了,命还得要。
结果他能憋到凌晨2点破防,直接来一句“搞不明白就离职”,还把“已读不回”跟旷工绑一块,属实给我看笑了。旷工这个词是这么用的吗,劳动法看了都得坐起来。
最真实的是,群里估计那一刻更安静了,没一个人敢接,心里都在默默更新简历。你说他是真不懂,还是装不懂,反正这种领导一开口,第二天工位上空气都得发紧。
算法题:汉明距离总和
这题我第一次看到题面,第一反应不是怎么两两去算,而是先看数据范围。只要 n 稍微上来一点,暴力两层循环基本就没法看了。因为“汉明距离总和”本质上是在问:数组里任意两个数,二进制位上有多少个位置不同,把这些不同全加起来。
很多人上来就会写这种:
deftotal_hamming_distance(nums):
ans = 0
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
ans += (nums[i] ^ nums[j]).bit_count()
return ans
逻辑没毛病,a ^ b 之后统计 1 的个数,就是这两个数的汉明距离。但这玩意儿是 O(n^2),数据一大就直接超时。算法题里这种写法,我一般只拿来验证样例,不拿来交。
这题真正该盯住的是“按位统计”。
因为任意一位上,假设有 c1 个数这一位是 1,c0 个数这一位是 0,那这一位对答案的贡献就是 c0 * c1。理由很直接:只要一对数字在这一位一个是 0,一个是 1,这一对就会贡献 1。那能组成多少对?就是这两个数量直接相乘。
所以整题就不用枚举数对了,改成枚举二进制位。
拿 nums = [4, 14, 2] 说一下:
4 -> 010014 -> 11102 -> 0010
比如倒数第二位,两个 1,一个 0,这一位就贡献 2 * 1 = 2。 每一位都这么算,最后累加就是答案。
Python 写出来很短:
deftotal_hamming_distance(nums):
ans = 0
n = len(nums)
for bit in range(32):
ones = 0
for x in nums:
ones += (x >> bit) & 1
ans += ones * (n - ones)
return ans
这段代码不花,现场感也够。外层固定扫 32 位,内层扫数组,所以时间复杂度是 O(32 * n),也就是 O(n);额外空间 O(1)。
这里还有个小细节,为什么是 32 位?因为题目里的整数通常不会超过 32 位范围。要是你写得更稳一点,也可以先取最大值的二进制长度:
deftotal_hamming_distance(nums):
ifnot nums:
return0
ans = 0
high = max(nums).bit_length()
for bit in range(high):
ones = sum((x >> bit) & 1for x in nums)
ans += ones * (len(nums) - ones)
return ans
这题难点不在位运算本身,在于能不能把“两两比较”这个思路掰开。很多题看着是在求组合,实际上拆到“每一位各自贡献多少”就顺了。
算法题做到后面,你会越来越有这种感觉:别急着写双层循环,先怀疑它。只要题目里出现“任意两两”“总和”“异或”“二进制位”,大概率就该往按位统计上靠了。这个路子,比死算靠谱得多。