中级 · 位运算
数一数 1 的个数
一个数的二进制里有多少个 1?这叫 popcount,而且它在真实 CPU 里是一条指令。
一句话答案每次把 n 右移一位并累加 n & 1 来数 1 的个数 —— 或者用 n &= n - 1 每步清掉一个 1,循环次数正好等于 1 的个数。
题目
输入: 一个非负整数(0 <= n <= 1000000)。
输出: n 的二进制中 1 的个数。
样例:
| 输入 | 输出 |
|---|---|
7 | 3 |
你的解答
「运行我的输入」用的自定义输入
判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。
卡住了?提示
提示 1 — 把余数加起来
n > 0 时:ones += n % 2; n /= 2;。二进制表示里打印的余数就是这些位。
提示 2 — Kernighan 技巧
while (n) { n &= n - 1; ones++; } 每圈清掉最低位的 1 —— 循环次数等于 1 的个数。
题解(先自己试试!)
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int ones = 0;
while (n > 0) {
ones += n % 2;
n /= 2;
}
cout << ones << endl;
return 0;
}
现代 C++ 有 std::popcount(n)(C++20),编译成一条 CPU 指令。你刚手写了一遍它。
深度解析
为什么这样做对
数 1 的三种写法——移位判断、Kernighan 的 n&(n-1)、硬件 popcount——正好是一次「同一答案、三种代价」的速览。
代价
朴素:恒定 32 轮。Kernighan:轮数等于 1 的个数。硬件:一条指令 O(1)。
常见错法
- 对有符号负数右移是实现定义行为,符号位会被不断补回来,循环可能永不结束。
n >>= 1用在负数上永远到不了 0。用unsigned。
下一步
让这些位派上用场:二进制表示。
记住这两句
- Kernighan:按 1 的个数循环,不是按位数循环。
- 只对无符号数移位——有符号右移是实现定义行为。
🐞 这段是错的 —— 点开,亲手让它挂一次
只数了低 8 位,大数就漏了
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int c = 0;
for (int i = 0; i < 8; i++) {
if (n % 2 == 1) c++;
n /= 2;
}
cout << c << endl;
return 0;
}