位运算
位异或
- 异或的性质:两个数字异或的结果a^b是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是如果同一位的数字相同则为 0,不同则为 1。
- 异或的规律:(1)任何数和本身异或则为0;(2)任何数和 0 异或是本身;(3)异或满足交换律。 即 a ^ b ^ c ,等价于 a ^ c ^ b。
只出现一次的数字(其余都出现两次) -> (1)可以用unordered_map记录数组元素出现的次数;(2)更快的方式是直接对所有元素进行异或操作,由于其他元素都出现两次(任何数和本身异或则为0),而且异或满足交换律,所以其他元素异或的结果应该为0,最后结果为这个只出现一次的数。
int singleNumber(vector<int>& nums) {
int k = 0; // 性质2,任何数和0异或是本身,所以初始值取0
for(int i = 0; i < nums.size(); i++){
k^=nums[i];
}
return k;
}
vector<int> singleNumbers(vector<int>& nums) {
int k = 0;
for(int num:nums){
k^=num;
}
// 假设这两个数字为a和b,此时k=a^b
// 将nums里的数字分两组,目标是a和b在不同的组里
int pos = 0, a = 0, b = 0;
for(int i = 0; i < 32;i++){
if(k & 1 == 1){ // 找到k中为1的位,说明a和b在该位是不同的
pos = i;
break;
}
k = k >> 1;
}
// 根据找到的pos进行分组,a和b在不同组
for(int num:nums){
if((num >> pos) & 1 == 1){
a^=num;
}else{
b^=num;
}
}
vector<int> v{a,b};
return v;
}
找到数组中只出现一次的数(其余都出现三次) -> (1) 哈希表存储和查找,使用unordered_map;(2) 如果某个数字出现3次,那么这个3个数字的和肯定能被3整除,则其对应二进制位的每一位的和也能被3整除;统计数组中每个数字的二进制中每一位的和,判断该和是否能被3整除。若可以,则只出现一次的数字的二进制数中那一位为0,否则为1。参考
int singleNumber(vector<int>& nums) {
int k = 0;
for(int i = 0; i < 32; i++){
int count = 0;
for(int num:nums){
if((num>>i)&1 == 1){
count++;
}
}
// 出现三次的所有数字的每一位上的和都能被3整除,如果count%3==1表明这个只出现一次的数在这一位为1,否则为0
if(count%3==1){
k += 1<<i;
}
}
return k;
}
相关练习
二进制中1的个数
不用加减乘除做加法 -> 位异或操作可以模拟二进制无进位的加法,位与操作可以模拟二进制的进位(位与的结果需要左移1位)。a可以当做无进位的和,b当做进位,然后不断重复之前的操作,直到进位b为0,a便是和。
int add(int a, int b) {
int sum, carry;
while(b!=0){
sum = a^b;
carry = (unsigned int)(a&b) << 1; // C++负数无法左移,需要转换成无符号整型
a = sum;
b = carry;
}
return a;
}