位运算作为计算机底层最基础的操作之一,在算法优化和系统编程中扮演着重要角色。简单来说,位运算就是直接对整数在内存中的二进制位进行操作。常见的位运算符包括与(&)、或(|)、异或(^)、取反(~)、左移(<<)和右移(>>)。
在实际编程中,位运算通常用于以下场景:
注意:不同编程语言对位运算的处理可能略有差异,特别是在处理负数时。Java和Python中的整数是有符号的,而C/C++中则取决于具体实现。
补数(Complement)是指对于一个给定的数字,其补数是该数字在特定基数下的"补全数"。在二进制中,补数分为两种:
以数字5为例(假设用4位表示):
十进制整数的反码(1009题)与二进制补数类似,但基于十进制系统。对于一个n位十进制数N,其反码定义为:
code复制反码 = (10^n - 1) - N
例如,数字5的1位十进制反码是(10^1 - 1) - 5 = 4
虽然476题和1009题分别处理二进制补数和十进制反码,但它们的核心思想是一致的:找到一种"互补"的数字表示。理解这种对应关系有助于我们统一处理类似问题。
给定一个正整数,输出其补数。补数是对该数的二进制表示取反(不包括前导零)。
示例:
解决这个问题的关键在于:
以数字5为例:
java复制public int findComplement(int num) {
int mask = 1;
while (mask < num) {
mask = (mask << 1) | 1;
}
return num ^ mask;
}
优化思路:
该算法的时间复杂度为O(1),因为整数的位数是固定的(如32位)。虽然循环次数取决于数字的大小,但最多循环32次。
每个非负整数N都有其二进制补数,但也可以定义其十进制反码。十进制反码是将N的每一位数字d替换为9-d。
示例:
解决这个问题需要考虑:
特殊案例处理:
java复制public int bitwiseComplement(int N) {
if (N == 0) return 9;
int result = 0;
int place = 1;
while (N > 0) {
int digit = N % 10;
result += (9 - digit) * place;
place *= 10;
N /= 10;
}
return result;
}
可以使用数学方法避免字符串转换:
判断奇偶:
java复制boolean isOdd = (num & 1) == 1;
交换两个数:
java复制a ^= b;
b ^= a;
a ^= b;
取绝对值:
java复制int mask = num >> 31;
int abs = (num ^ mask) - mask;
运算符优先级问题:
移位运算的陷阱:
整数溢出:
使用位运算代替乘除法:
使用位掩码代替布尔数组:
查表法:
补数运算在简单的加密算法中很常见。例如,可以使用异或运算实现基础的对称加密:
java复制// 简单加密
int encrypt(int data, int key) {
return data ^ key;
}
// 解密(与加密相同)
int decrypt(int encrypted, int key) {
return encrypted ^ key;
}
十进制反码常用于校验和计算。例如,在银行账号验证中,可能会使用反码作为校验位:
权限控制系统:
java复制final int READ = 1 << 0; // 0001
final int WRITE = 1 << 1; // 0010
final int EXEC = 1 << 2; // 0100
// 设置权限
int permissions = READ | WRITE;
// 检查权限
boolean canWrite = (permissions & WRITE) != 0;
图形处理中的颜色操作:
位运算有一些有趣的数学性质:
理解这些性质可以帮助我们设计更高效的算法。例如,利用异或性质可以解决"找出数组中唯一出现一次的数字"问题。