妙用位运算:解构汉明距离至100%(提供分析与多解)


名称:Doubletful的博客 💠算法专栏 签名:路漫漫其修远兮,吾将上下而求索


文章目录

前言

在数字世界的底层,万物皆为二进制,当我们比较两个整数时,一个有趣的度量方式不是看它们数值差多少,而是看它们在二进制形态下"长得有多不像",这种度量在信息论中被称为汉明距离。

文章概述:

  1. 本文将使用 c 语言通解 LeetCode 题目:461. 汉明距离。
  2. 将提供三种逐步优化的解法使时间复杂度缩短至 O(1)。
  3. 提供基础位运算简介以便初学者阅读,可跳过。

一、二进制计算简介

1.二进制操作符

1.1.位操作符

🔹按位与 &:按照对应的二进制位进行与运算,同时为1才为1,

二进制例:1100 & 0011 == 0000,1010 & 1000 == 1000。

🔸按位或 |:按照对应的二进制位进行或运算,同时为0才为0,

二进制例:1100 | 0011 == 1111,1010 | 1000 == 1010。

🔹按位异或 ^:按照对应的二进制位进行异或运算,相同为0,相异为1,

二进制例:1100 ^ 0011 == 1111,1010 ^ 1000 == 0010。

🔸按位取反 ~:按照对应的二进制位进行取反运算,将0和1互换,

二进制例:~1100 == 0011,~1010 == 0101,~1000 == 0111。

1.2.移位操作符

🔹左移操作符 <<:

语法为:被移动整数 << 移动位数,规则是左边丢弃,右边填充0。

二进制例:00011000 << 1 == 00110000。

🔸右移操作符 >>:

语法为:被移动整数 >> 移动位数,规则分为两种情况,逻辑右移和算术右移,大部分编译器采用算术右移。逻辑右移:左边填充0,右边丢弃。算术右移:左边用该值的符号位填充,右边丢弃。

二进制例:00011000 >> 1 == 00001100。

:位操作符和移位操作符都只能对整数进行操作。

2.进制转换

⇒二进制转八进制:

从二进制的低位开始每3个二进制位转换为一个八进制位,剩余直接转化。

例:二进制为 10111,八进制为 27,十进制为 23。

⇒二进制转十六进制:

从二进制的低位开始每4个二进制位转换为一个十六进制位,剩余直接转化。

例:二进制为 1010011,十六进制为 53,十进制为 83。

接下来,是正题部分:

二、题目

先看题目:

我们被要求计算两个 32 位整数的汉明距离,即 x 与 y 不同二进制位的个数。

解法一:>> 操作符遍历统计

首先计算 x ^= y。异或运算的本质是"相同为0,不同为1",因此新 x 的二进制表示中有多少个 1,就代表原数有多少个不同的位。利用 >> 操作符遍历 x 的每一位并使用 & 操作符判断 x 的每一位是否为 1 记录不同位的个数。

解法二:Brian Kernighan 算法遍历统计

有一个极其巧妙的位运算性质:n & (n - 1) 的结果会将 n 的二进制中最右边的 1 变为 0,图例如下:

设有一个二进制数为 10000,当这个数减 1 时结果是 01111,10000 & 01111 正好等于 0,因为每次减 1 都会使二进制数上最右边的 1 退位,使在该数位前的全 0 位变为全 1 位并将该数位变为 0,& 操作正好就将减 1 前的二进制数与减 1 后的二进制数有 1 的位全部对应有 0 的位,抹除最右边的 1。

解法三:掩码分治统计

这是本篇文章的重头戏,也是计算机底层架构(如 CPU 指令 POPCNT)实现统计 1 个数的数学原理。它不再依赖循环,而是通过二分法将位数累加。

初看代码会非常奇怪,这些十六进制数代表什么意思,为什么 x 要 & 这些数后又将 x 右移并再次 & 它们,为什么这样就能计算新 x 中为 1 的二进制位个数了------我们逐步拆解,先看这些十六进制数代表什么:

一个十六进制位等于四个二进制位,将这些十六进制数转换为二进制后:

  • 0x55555555 = 0101 0101 0101 0101 0101 0101 0101 0101
  • 0x33333333 = 0011 0011 0011 0011 0011 0011 0011 0011
  • 0x0f0f0f0f = 0000 1111 0000 1111 0000 1111 0000 1111
  • 0x00ff00ff = 0000 0000 1111 1111 0000 0000 1111 1111
  • 0x0000ffff = 0000 0000 0000 0000 1111 1111 1111 1111

可以观察到有一定规律,它们的二进制特征分别是:

  • 0x55555555(单个位交替,用于隔离相邻的奇偶位)
  • 0x33333333(两位交替,用于隔离相邻的 4 位中的低 2 位)
  • 0x0F0F0F0F(四位交替,用于隔离相邻的 8 位中的低 4 位)
  • 0x00FF00FF(八位交替,用于隔离相邻的 16 位中的低 8 位)
  • 0x0000FFFF (十六位交替,用于隔离高 16 位和低 16 位)

这些位中的 0 都用于隔离,1 都用于保留计算,0x55555555 的作用有些特殊,它先以每 1 位统计 x 中 1 的个数并将个数存储进对应的两位中,也就是先转换,将代表 x 与 y 在哪一位不同的 1 转换为以每两位分隔的实际不同位的数量,注意该意义转换目前只在每两位中生效

而除 0x55555555 以外的值都在做被分隔的实际不同位数量的合并操作,最终合并为 32 位表示的实际不同位数量,也就是正确的十进制表示。

& 运算、>> 运算与加法解读:

在理解了这些数字在代码中的作用后,再看 x 分别直接进行 & 运算和 x 先右移再进行 & 运算就非常通俗易懂了,如下所示:

❇️2 位一组:将 x 分为每 2 位一组,计算每组中 1 的个数。

x & 0x55555555 取出奇数位的 1,右移后取出偶数位的 1,二者相加,结果以二进制形式保存在原来的 2 位空间中。

❇️4 位一组:将上一步的结果每 4 位一组,计算每组中 1 的个数。

x & 0x33333333 先取出前两位的个数,右移后再取出后两位的个数,最后相加,结果以二进制形式保存在原来的 4 位空间中。

........................

❇️归并到 32 位:最终得到整个 32 位整数中 1 的总数。

三、总结

通过这道经典的汉明距离题目,我们清晰地看到了算法演进的三个层次:

解法 核心思想 优点 缺点
解法一 逐位右移检查 直观易懂,符合第一反应 固定循环 32 次,存在冗余操作
解法二 清除最低位的 1 循环次数仅与 1 的个数有关,实战效率高 依然依赖循环,无法利用现代 CPU 并行特性
解法三 二进制并行加法,树状归并 无分支、无循环,性能极致,体现了位运算的数学之美 可读性较差,需要深刻理解二进制分治原理

四、练习

推荐手动在LeetCode网站上实现三种解法:

  1. 汉明距离 && 链接:link.

⚛️EL PSY CONGROO,十分感谢你的阅读

本期不确定:

是否应该添加掩码分治统计执行图例

相关推荐
Keven_111 小时前
AcWing算法提高课思路速查:搜索
算法·搜索
沙蒿同学2 小时前
当古诗词遇上 AI:从 38 万句诗词中取一个好名字
python·算法·架构
变量未定义~2 小时前
单调栈、单调队列(模板)、子矩阵(模板)
数据结构·算法·蓝桥杯
不会就选b2 小时前
算法日常・每日刷题--<快速排序>2
数据结构·算法
CClaris2 小时前
大模型量化从0到1(五):GPTQ 原理详解 + 从零量化一个真实大模型
人工智能·python·算法·机器学习
凯瑟琳.奥古斯特3 小时前
力扣1012数位DP解法详解
开发语言·c++·算法·leetcode·职场和发展
shx_wei3 小时前
从函数调用到进程替换:使用 execl、CMake 与 PowerShell 构建一个多可执行程序
linux·c语言·windows·c
大鱼>3 小时前
AI+货物追踪:贵重物品智能追踪系统
人工智能·深度学习·算法·机器学习
大鱼>3 小时前
AI+货物追踪:集装箱智能追踪系统
人工智能·深度学习·算法·机器学习