1. 常见的运算总结
1.1. 基础位运算
按位与(&):有 0 就是 0
按位或(|):有 1 就是 1
按位异或(^):相同为 0,相异为 1 / 无进位相加
按位取反(~):逐位翻转,0→1,1→0
左移(<<):整体左移,右侧补 0
右移(>>):整体右移,左侧补符号位(正补 0,负补 1)
1.2 给一个数n,确定它的二进制表示中的第 x 位是 0 还是 1
bash
(n >> x) & 1
1.3 将一个数 n 的二进制表示的第 x 位修改成1
bash
n |= (1 << x)
1.4 将一个数 n 的二进制表示的第 x 位修改成 0
bash
n &= (~(1 << x))
1.5 位图的思想
位图的核心思想是用二进制位(bit)表示数据状态,通过每一位的 0/1 标记 "不存在 / 存在",将离散数据映射到连续位空间,利用位运算实现高效存储与操作。
1.6 提取一个数 n 二进制中最右侧的1
bash
n & -n
1.7 干掉一个数 n 二进制表示中最右侧的 1
bash
n & (n - 1)
1.8 异或(^)运算的运算律
bash
a ^ 0 = a
a ^ a = 0
a ^ b ^ c = a ^ (b ^ c)
2.例题
2.1 面试题 01.01. 判定字符是否唯一
面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)

cpp
bool isUnique(string astr) {
// 运用位图的算法思想可以很轻松的解决这道题
// 在整数的32bit位中一个bit位对应一个字符
// 通过二进制位的 0/1 表示字符 "没出现过 / 出现过"
if(astr.size() > 26) return false;// 当字符串长度大于26必然会出现重复的字符,直接返回false
int bitMap = 0;
for(auto ch : astr)
{
int i = ch - 'a';
if((bitMap >> i) & 1) return false;// 字符对应的字节位置为1时说明字符重复出现
bitMap |= (1 << i);// 给字符对应的字节位置标1
}
return true;
}
2.2 丢失的数字

cpp
int missingNumber(vector<int>& nums) {
int bitMap = 0;
for(int i = 0; i <= nums.size(); ++i)
bitMap ^= i;
for(auto i : nums)
bitMap ^= i;
return bitMap;
}
2.3 两整数之和

cpp
int getSum(int a, int b) {
// (a ^ b)可以进行无进位相加,(a & b) << 1可以把进位提出来。
// 用进位与上一个无进位相加的结果进行无进位相加,继续把相加的进位提出来。
// 以此循环知道进位为零为止。
int ret = a ^ b;
int Carry = (a & b) << 1;
while (Carry)
{
int tmp = ret;
ret = ret ^ Carry;
Carry = (tmp & Carry) << 1;
}
return ret;
}
2.4 只出现一次的数字 II
137. 只出现一次的数字 II - 力扣(LeetCode)

cpp
int singleNumber(vector<int>& nums) {
// 将所有数的相同二进制位对应的1或0相加
// 假设某个元素出现三次,且其对应的二进制位为1
// 我们只需要将相加的结果模3就可以把出现三次的元素去掉,
// 那么剩下就是仅出现一次的元素对应的二进制位的结果
int ans = 0;
for(int i = 0; i < 32; ++i)
{
int sum = 0;
for(auto x : nums)
if((x >> i) & 1) ++sum;
sum %= 3;
ans |= (sum << i);
}
return ans;
}
2.5 只出现一次的数字 III

cpp
vector<int> singleNumber(vector<int>& nums) {
// 假设nums中只出现一次的元素分别是x1 和 x2。
// 将所有元素进行异或,最后剩下的只有x1^x2
// 提取x1^x2的二进制中最右侧的1,设其为第 l 位,
// 那么x1和x2中的某一个数的二进制表示的第 l 位为 0,
// 另一个数的二进制表示的第 l 位为 1。
// 这样我们可以根据元素的二进制表示的第 l 位为 0 还是 1 分成两类
// x1 和 x2分别在这两个不同的类,最后将这两类数分别进行异或操作
// 则可以分别得到 x1 和 x2
// 把所有元素分为两类,一类包含x1 另一类包含x2
int a = 0;
for(int i = 0; i < nums.size(); ++i)
a ^= nums[i];
// INT_MIN取负会导致整数溢出
int x = (a == INT_MIN ? a : a & -a);
int x1 = 0, x2 = 0;
for(int i = 0; i < nums.size(); ++i)
{
if(nums[i] & x) x1 ^= nums[i];
else x2 ^= nums[i];
}
return {x1, x2};
}
2.6 面试题 17.19. 消失的两个数字
面试题 17.19. 消失的两个数字 - 力扣(LeetCode)

根据题目2.2 和 2.5可以很轻松的解决这道题。
cpp
vector<int> missingTwo(vector<int>& nums) {
int n = nums.size();
int xorsum = 0;
for(int i = 1; i <= n + 2; ++i) xorsum ^= i;
for(auto num : nums) xorsum ^= num;
int x = xorsum == INT_MIN ? xorsum : xorsum & (-xorsum);
int x1 = 0, x2 = 0;
for(int i = 1; i <= n + 2; ++i)
{
if(i & x) x1 ^= i;
else x2 ^= i;
if(i <= n)
{
if(nums[i - 1] & x) x1 ^= nums[i - 1];
else x2 ^= nums[i - 1];
}
}
return {x1, x2};
}