Python技术迷

某大厂员工吐槽:公司领导夜里10点发群消息,大家都是已读不回。哪知凌晨2点,领导在群里发飙:以后“已读不回”一律按旷工处理。

刚刷到这个帖,我第一反应就是,领导这班味儿已经冲出屏幕了。晚上10点往群里丢消息,大家已读不回,其实意思已经很明显了:哥几个下班了,命还得要。

Image

结果他能憋到凌晨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  -> 0100
  • 14 -> 1110
  • 2  -> 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

这题难点不在位运算本身,在于能不能把“两两比较”这个思路掰开。很多题看着是在求组合,实际上拆到“每一位各自贡献多少”就顺了。

算法题做到后面,你会越来越有这种感觉:别急着写双层循环,先怀疑它。只要题目里出现“任意两两”“总和”“异或”“二进制位”,大概率就该往按位统计上靠了。这个路子,比死算靠谱得多。