E191.位1的个数
bit manipulation, divide and conquer, https://leetcode.cn/problems/number-of-1-bits/
给定一个正整数 n,编写一个函数,获取一个正整数的二进制形式并返回其二进制表达式中 设置位 的个数(也被称为汉明重量)。
汉明重量是符号序列中非零符号的数量,在二进制数据中特指1的个数
示例 1:
输入:n = 11
输出:3
解释:输入的二进制串 1011 中,共有 3 个设置位。示例 2:
输入:n = 128
输出:1
解释:输入的二进制串 10000000 中,共有 1 个设置位。示例 3:
输入:n = 2147483645
输出:30
解释:输入的二进制串 1111111111111111111111111111101 中,共有 30 个设置位。提示:
1 <= n <= 2^31 - 1
进阶:
- 如果多次调用这个函数,你将如何优化你的算法?
这个问题是关于计算一个正整数二进制表示中 1 的个数(汉明重量)。
下面为您提供几种不同的解决方法,以及针对“多次调用”进阶问题的优化思路。
方法一:Brian Kernighan 算法(最经典的位运算方法)
思路: 利用 n & (n - 1)。这个操作可以消除二进制表示中最低位的 1。 例如:对于 n = 12 (1100):
n - 1 = 11 (1011),1100 & 1011 = 1000(清除了最低位的1)。- 重复上述操作,直到
n变为0。循环执行的次数就是二进制中1的个数。
这种方法的时间复杂度取决于 1 的个数,而不是二进制的位数,因此非常高效。
代码实现:
class Solution:
def hammingWeight(self, n: int) -> int:
count = 0
while n:
n &= (n - 1)
count += 1
return count- 时间复杂度:
,其中 是 n的二进制表示中1的个数,最坏情况下为。 - 空间复杂度:
。
方法二:利用内置函数(最简短的 Pythonic 方法)
思路: 在 Python 3.10 及以上版本中,可以直接使用 int.bit_count() 方法。在旧版本中,也可以使用 bin(n).count('1')。
代码实现:
class Solution:
def hammingWeight(self, n: int) -> int:
return n.bit_count() # Python 3.10+
# 或者使用:return bin(n).count('1')- 时间复杂度:
。 - 空间复杂度:
(若使用 bin,则为字符串开销)。
进阶:如何应对多次调用(优化方案)
如果这个函数被频繁、海量地调用,可以通过查表法(Lookup Table)进行空间换时间的优化。
思路: 一个 32 位的整数可以被拆分为 4 个 8 位的字节(Byte),或者 2 个 16 位的半字。 我们可以预先计算好所有 8 位整数(0 到 255)中 1 的个数,并存入一个长度为 256 的数组。 对于输入的任意 32 位整数,我们只需要将其拆分为 4 个字节,分别查表,然后将结果相加。
这样,每次查询只需要进行 4 次移位/按位与操作以及 4 次数组查询。
代码实现:
class Solution:
# 类变量:只在类加载时初始化一次 256 大小的查找表
# 也可以使用 @cache / @lru_cache 装饰器实现缓存
_lookup = [0] * 256
for i in range(256):
_lookup[i] = (i & 1) + _lookup[i >> 1]
def hammingWeight(self, n: int) -> int:
# 将 32 位整数拆分为 4 个 8 位组分别查表
return (
self._lookup[n & 0xFF] +
self._lookup[(n >> 8) & 0xFF] +
self._lookup[(n >> 16) & 0xFF] +
self._lookup[(n >> 24) & 0xFF]
)- 初始化时间复杂度:
- 单次查询时间复杂度:
- 空间复杂度:
存储查找表。
如果多次调用的参数重复率极高,还可以直接利用 Python 的 @functools.lru_cache 装饰器对函数进行缓存,以便直接返回已计算过的值。