位运算技巧:代码优化与高频面试题 ⭐⭐
🎯 面试题:如何用位运算判断一个数是否为 2 的幂次方?
位运算是代码优化利器,在算法面试和系统设计中都很重要。
一、位运算基础
// 六种基本位运算
int a = 6, b = 3;
// 与(AND):两位都为 1 才为 1
a & b // 110 & 011 = 010 = 2
// 或(OR):任一位为 1 就为 1
a | b // 110 | 011 = 111 = 7
// 异或(XOR):相同为 0,不同为 1
a ^ b // 110 ^ 011 = 101 = 5
// 取反(NOT):0 变 1,1 变 0
~a // ~6 = -7(补码取反)
// 左移
a << 1 // 6 << 1 = 12(乘以 2^n)
// 右移(算术右移,正数高位补 0,负数高位补 1)
a >> 1 // 6 >> 1 = 3(除以 2^n,向下取整)
-6 >> 1 // -6 >> 1 = -3
// 无符号右移(高位始终补 0)
-6 >>> 1 // 巨大的正数(符号位变为 0)
二、常用技巧
// 1. 判断奇偶数(比 % 2 快)
if ((n & 1) == 1) // n 是奇数
if ((n & 1) == 0) // n 是偶数
// 2. 交换两数(不用临时变量)
a = a ^ b; // a = a ^ b
b = a ^ b; // b = (a ^ b) ^ b = a
a = a ^ b; // a = (a ^ b) ^ a = b
// 3. 获取第 i 位(从 0 开始)
int bit = (n >> i) & 1;
// 4. 设置第 i 位为 1
n = n | (1 << i);
// 5. 清除第 i 位(设为 0)
n = n & ~(1 << i);
// 6. 更新第 i 位为 v(v 为 0 或 1)
n = (n & ~(1 << i)) | (v << i);
// 7. 取最低位的 1(lowbit)
int lowbit = n & (-n);
// n = 12 = 1100
// -n = ~n + 1 = 0011 + 1 = 0100
// lowbit = 1100 & 0100 = 0100 = 4
// 8. 判断是否为 2 的幂次方
// n > 0 && (n & (n-1)) == 0
// 原理:2^n 只有一位是 1,n-1 把那一位变成 0,后面全变 1,& 运算为 0
三、面试真题
题目 1:二进制中 1 的个数
// 方法一:Brian Kernighan 算法(最优 O(k),k 为 1 的个数)
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n = n & (n - 1); // 消除最低位的 1
count++;
}
return count;
}
// 方法二:标准算法(O(32))
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
count += (n & 1);
n >>>= 1;
}
return count;
}
// 方法三:Integer.bitCount()(Java 内置)
return Integer.bitCount(n);
题目 2:两数之和(不用 + - * /)
// 用异或实现加法
public int add(int a, int b) {
while (b != 0) {
int carry = (a & b) << 1; // 进位
a = a ^ b; // 非进位和
b = carry;
}
return a;
}
// 减法:a - b = a + (-b)
public int subtract(int a, int b) {
return add(a, add(~b, 1));
}
// 乘法:
public int multiply(int a, int b) {
boolean neg = (a < 0) ^ (b < 0);
a = Math.abs(a);
b = Math.abs(b);
int result = 0;
while (b > 0) {
if ((b & 1) == 1) result = add(result, a);
a <<= 1;
b >>= 1;
}
return neg ? -result : result;
}
题目 3:只出现一次的数字 II
// 数组中除了一个数字外,其他数字都出现 3 次
// 要求找到那个只出现 1 次的数字
// 思路:统计每一位上 1 的个数 % 3,余数就是答案对应位
public int singleNumber(int[] nums) {
int result = 0;
for (int i = 0; i < 32; i++) {
int bitCount = 0;
for (int num : nums) {
bitCount += (num >> i) & 1;
}
if (bitCount % 3 != 0) {
result |= (1 << i);
}
}
return result;
}
题目 4:位图排序(海量数据去重)
// 用 BitSet 对 40 亿整数排序/去重(只需要 512MB 内存)
// int 范围:-2^31 ~ 2^31-1,共 2^32 个数
// BitSet 需要 2^32 / 8 = 512MB
public class Bitmap {
private long[] bits; // 1 bit 表示一个数字
private int max;
public Bitmap(int max) {
this.max = max;
this.bits = new long[(max >> 6) + 1]; // /64
}
public void add(int n) {
bits[n >> 6] |= (1L << (n & 63)); // n % 64
}
public boolean contains(int n) {
return (bits[n >> 6] & (1L << (n & 63))) != 0;
}
// 使用示例:排序 100 万个范围在 [1, 1000万] 的整数
public static void sort(int[] nums) {
Bitmap bitmap = new Bitmap(10_000_000);
for (int n : nums) bitmap.add(n);
for (int i = 0; i < bitmap.bits.length; i++) {
for (int j = 0; j < 64; j++) {
if ((bitmap.bits[i] & (1L << j)) != 0) {
System.out.println((i << 6) + j);
}
}
}
}
}
题目 5:找出重复的数字(位运算法)
// 长度为 n+1 的数组,值为 1~n,找出重复数字
// O(1) 空间,O(n log n) 时间
public int findDuplicate(int[] nums) {
int result = 0;
for (int i = 0; i < 32; i++) {
int bit = 1 << i;
int count1 = 0, count2 = 0;
for (int j = 0; j < nums.length; j++) {
if ((nums[j] & bit) != 0) count1++;
}
for (int j = 1; j <= nums.length - 1; j++) {
if ((j & bit) != 0) count2++;
}
if (count1 > count2) {
result |= bit;
}
}
return result;
}
四、位运算在系统设计中的应用
// 1. 权限系统:用位掩码表示权限
public class Permission {
public static final int READ = 1 << 0; // 0001
public static final int WRITE = 1 << 1; // 0010
public static final int DELETE = 1 << 2; // 0100
public static final int ADMIN = 1 << 3; // 1000
private int permissions;
public void grant(int p) { permissions |= p; }
public void revoke(int p) { permissions &= ~p; }
public boolean has(int p) { return (permissions & p) == p; }
}
// 使用
Permission p = new Permission();
p.grant(READ | WRITE);
p.has(READ); // true
p.has(DELETE); // false
p.revoke(WRITE);
// 2. 状态机:用位掩码表示复合状态
// 用户同时有 VIP + 活跃 + 已验证状态
int state = VIP | ACTIVE | VERIFIED;
boolean isVipActive = (state & (VIP | ACTIVE)) != 0;
五、面试高频题
Q1: 如何判断一个数是否为 2 的幂次方?
核心思想:
n > 0 && (n & (n-1)) == 0。原理:2 的幂次方二进制只有一位为 1(如 8=1000),减 1 后变成 0111,两者 & 运算为 0。如果是负数要先特殊处理。另外n > 0 && (n & -n) == n也可判断,-n 的最低位 1 和 n 的最低位 1 是同一位。
Q2: 异或运算有哪些性质?
四个性质:① 归零律
a ^ a = 0;② 恒等律a ^ 0 = a;③ 交换律a ^ b = b ^ a;④ 结合律(a ^ b) ^ c = a ^ (b ^ c)。应用:不借助临时变量交换两数(a = a ^ b; b = a ^ b; a = a ^ b)、找出出现奇数次的数字(全员异或)。
Q3: 布隆过滤器如何用位运算实现?
布隆过滤器用 k 个哈希函数将 key 映射到 m 位的位数组,将对应 k 个位置设为 1。查询时检查 k 个位置是否全为 1,全为 1 则可能存在,否则一定不存在。空间效率高(1 bit 存一个状态),时间 O(k)。位数组大小选择:
m = -n * ln(p) / (ln(2)^2),其中 n 为预估元素个数,p 为期望误判率。