前言
-
汉明距离广泛应用于多个领域。在编码理论中用于错误检测,在信息论中量化字符串之间的差异。
-
两个整数之间的汉明距离是对应位置上数字不同的位数。
-
根据以上定义,我们使用异或运算,记为
⊕
(键盘上"^"符号,在数字6的上边),当且仅当每个输入位不同时输出为1
。
-
计算
x
和y
之间的汉明距离,可以先计算 x⊕y
,然后统计结果中等于 1 的个数
。 -
现在,原始问题转换为
位计数问题
。位计数有多种思路,将在下面的方法中介绍。
方法一:内置位计数功能
思路及算法
大多数编程语言都内置了计算二进制表达中1的数量的函数。在工程中,我们应该直接使用内置函数。
Java 中的位操作
static int Integer.bitCount(); // 统计 1 的数量
static int Integer.highestOneBit(); // 获得最高位
static String toBinaryString(int i); // 转换为二进制表示的字符串
代码:
class Solution {
public int hammingDistance(int x, int y) {
return Integer.bitCount(x ^ y);
}
}
复杂度分析
时间复杂度:
O(1)。不同语言的实现方法不一,我们可以近似认为其时间复杂度为 O(1)。
空间复杂度:
O(1)。
方法二:移位实现位计数
思路及算法
在锻炼算法能力时,重复造轮子是不可避免的,也是应当的。因此读者们也需要尝试使用各种方法自己实现几个具有位计数功能的函数。本方法将使用位运算中移位的操作实现位计数功能。
具体地,记 s=x⊕y
,我们可以不断地检查s的最低位
,如果最低位为1,那么令计数器加一,然后我们令s整体右移
一位,这样s的最低位将被舍去,原本的次低位就变成了新的最低位。我们重复这个过程直到s=0
为止。这样计数器中就累计了s的二进制表示中1
的数量。
代码:
class Solution {
public int hammingDistance(int x, int y) {
int z = x ^ y;
int cnt = 0;
while(z != 0) {
if ((z & 1) == 1) cnt++;//z的最后一位是1
z = z >> 1;
}
return cnt;
}
}
复杂度分析
时间复杂度:
O(logC),其中C是元素的数据范围,在本题中 logC=log 231=31
空间复杂度:
O(1)
方法三:Brian Kernighan算法
思路及算法
在方法二中,对于 s=(10001100)2
的情况,我们需要循环右移8
次才能得到答案。而实际上如果我们可以跳过两个1
之间的0
,直接对1
进行计数,那么就只需要循环3
次即可。
我们可以使用Brian Kernighan算法
进行优化,具体地,该算法可以被描述为这样一个结论:记 f(x)
表示x
和x−1
进行与运算
所得的结果(即 f(x)=x & (x−1)
),那么f(x)
恰为x删去其二进制表示中最右侧
的1
的结果。
基于该算法,当我们计算出s=x⊕y
,只需要不断让 s=f(s)
,直到s=0
即可。这样每循环一次,s
都会删去其二进制表示中最右侧的1
,最终循环的次数即为s
的二进制表示中1
的数量。
代码:
//使用 z&(z-1) 去除 z 位级表示最低的那一位。
public int hammingDistance(int x, int y) {
int z = x ^ y;
int cnt = 0;
while (z != 0) {
z &= (z - 1);
cnt++;
}
return cnt;
}
复杂度分析
时间复杂度:
O(logC),其中C是元素的数据范围,在本题中 logC=log231=31
空间复杂度:
O(1)