LeetCode 191. 位1的个数:两种解法详解

0 阅读5分钟

在LeetCode的位运算题目中,191. 位1的个数(也称为汉明重量)是一道基础且经典的题目。它不仅考察对二进制的理解,更能帮助我们掌握位运算的核心技巧——从直观的暴力遍历到高效的位运算优化,两种解法的差距也能让我们深刻体会到“优化”在编程中的意义。

先明确题目要求:给定一个正整数 n,编写一个函数,获取其二进制形式中「设置位」(即值为1的位)的个数。比如,n=3(二进制 11),返回2;n=4(二进制 100),返回1。

下面我们逐一拆解两种解法,从思路、代码到优缺点,帮你彻底搞懂这道题。

解法一:暴力遍历法(右移 + 取余判断)

思路解析

最直观的思路的是:既然要统计二进制中1的个数,我们可以逐位判断每一位是否为1。具体步骤如下:

  1. 初始化一个计数器 res,用于记录1的个数,初始值为0;

  2. 用一个临时变量 cur 保存当前的n(也可以直接操作n,这里用cur是为了更清晰地展示过程);

  3. 循环判断 cur 是否不为0:每次用 cur % 2 取余,若余数为1,说明当前最低位是1,计数器res加1;

  4. 将 cur 右移1位(cur = cur >> 1),相当于把二进制的每一位依次“移到”最低位,方便下一次判断;

  5. 当 cur 变为0时,说明所有位都判断完毕,返回res即可。

代码实现

function hammingWeight_1(n: number): number {
  let res = 0;
  let cur = n;
  while (cur !== 0) {
    const one = cur % 2;
    if (one === 1) res++;
    cur = cur >> 1;
  }
  return res;
};

优缺点分析

优点:思路简单、容易理解,适合刚接触位运算的新手,代码可读性强,没有复杂的位运算技巧。

缺点:效率一般。假设n的二进制有k位,那么循环就要执行k次。比如n是2^31-1(二进制31个1),就需要循环31次,虽然实际运行速度也不慢,但存在优化空间。

解法二:高效位运算法(n & (n-1) 技巧)

这是本题的最优解,核心是利用位运算的特性,减少循环次数——循环次数等于二进制中1的个数,比解法一更高效。

核心技巧:n & (n - 1) 的作用

先记住一个关键结论:n & (n - 1) 会将n的二进制中「最右边的一个1」变成0

举个例子:

  • n = 6(二进制 110),n-1 = 5(二进制 101),n & (n-1) = 100(二进制),最右边的1(第2位)被变成0;

  • n = 4(二进制 100),n-1 = 3(二进制 011),n & (n-1) = 000,最右边的1(第3位)被变成0;

  • n = 7(二进制 111),n-1 = 6(二进制 110),n & (n-1) = 110,最右边的1(第1位)被变成0。

也就是说,每执行一次 n & (n-1),就会消除一个1。那么,我们只需要统计执行多少次这个操作,直到n变成0,次数就是1的个数。

思路解析

  1. 初始化计数器 res 为0;

  2. 循环判断n是否不为0:每次执行n = n & (n - 1),消除一个1,同时res加1;

  3. 当n变为0时,说明所有1都被消除,返回res。

代码实现

function hammingWeight_2(n: number): number {
  let res = 0;
  while (n) {
    n &= n - 1;
    res++;
  }
  return res;
};

优缺点分析

优点:效率极高。循环次数等于二进制中1的个数,比如n=2^31-1(31个1),只需要循环31次;如果n=0,循环0次,是最优解。代码简洁,体现了位运算的魅力。

缺点:思路相对抽象,需要理解n & (n-1) 的特性,新手可能需要多举例验证才能掌握。

两种解法对比

解法核心思路时间复杂度空间复杂度适用场景
暴力遍历法右移取余,逐位判断1O(k),k为二进制位数O(1)新手入门,理解二进制基础
高效位运算法n & (n-1) 消除最右1,统计次数O(m),m为二进制中1的个数O(1)面试最优解,追求高效

注意事项(避坑点)

  • 本题中n是正整数,若n可能为负数(比如JavaScript中数字是有符号的),右移操作可能会出现符号位补1的情况,导致死循环。但题目明确n是正整数,因此无需处理这种情况;

  • 解法一中,cur可以直接用n代替,简化代码(即去掉cur变量,直接操作n),效果一致;

  • 解法二中,循环条件是while(n),等价于while(n !== 0),因为当n为0时,布尔值为false,循环终止。

总结

LeetCode 191题虽然简单,但两种解法的差异体现了“思路优化”的重要性。暴力解法胜在直观,适合入门;而n & (n-1) 的技巧则是位运算中的经典用法,不仅能解决这道题,还能应用在其他位运算题目中(比如判断一个数是否是2的幂、统计两个数的二进制差异等)。