二进制中的位数:从最右边开始,第一个数记为第0位,第二个数记为第1位,以此类推。(为了方便与右移操作相匹配)
一、基础位运算
<<:左移
>>:右移
~:取反
&:按位与(有0就是0)
|:按位或(有1就是1)
^:按位异或(相同为0,相异为1,也就是无进位相加)
二、经典题目
(1)给一个数n,确定它二进制表示的第x位是0还是1
n>>x&1或者n>>x|0
(2)将一个数n的二进制表示的第x位修改为1
第x位修改为1:| 1
其余位保持不变:| 0
总体: n | (1<<x)
(3)将一个数n的二进制表示的第x位修改为0
第x位修改为0:& 0
其余位保持不变:& 1
总体: n & (~(1<<x))
(4)提取一个数n二进制表示中最右侧的1
n & -n
取负数操作相当于取反后加一:~n+1
目的:将原二进制数最右侧的1,左边区域全部变成相反(0和1互变),右边区域不变
(5)干掉一个数n二进制表示中最右侧的1
n & (n-1)
n-1作用:将最右侧的1和它的右侧全部取反。例如,最右侧位上的数为0,则需不断向左借位,以此达到效果
三、位图的思想
将一个int整形(32bit位)记成二进制,每一位都是0或1,就相当于一个数组可以用来记录信息
四、异或(^)运算律
1、a ^ 0 = a
2、a ^ a = 0(消消乐)
3、a ^ b ^ c = a ^ ( b ^ c )
五、例题
两整数之和
思路:
1、a ^ b表示a与b无进位相加的结果(如果该位上两数都是1则结果为0且不进位)
2、(a & b)<<1表示需要进位加1的位为1其余位为0:a & b表示a、b中只有该位上都是1结果才为1,否则结果为0,作用是将需要进位的当前位记为1,其余位记为0。由于进位要向左一位进,所以(a & b)后要<<1
3、a ^ b + (a & b)<<1就是a+b的结果,由于题目不允许直接相加,则继续将a ^ b视为新的a,(a & b)<<1视为新的b,继续用1、2的方法将新的a和b相加,不断重复此过程,直到新的b为0,则无需相加,新的a就是最终结果
class Solution {
public int getSum(int a, int b) {
while (b != 0) {
int x = a ^ b;
int carry = (a & b) << 1;
a = x;
b = carry;
}
return a;
}
}
只出现一次的数字II
思路:
1、二进制的每一位不是0就是1,nums中所有数的和sum也同理。
2、假设只出现一次的数为a,nums除a外,第i位为0的数共有3x个,第i位为1的数共有3y个,这些数第i位之和为3的倍数。加上a后,sum%3的结果和a的第i位数字相同。
3、用此方法,计算出a的第i位上的数,以此类推计算出a的值
class Solution {
public int singleNumber(int[] nums) {
int ret = 0;
for (int i = 0; i < 32; i++) {//i表示nums数组每个数需要右移的位数
int sum = 0;
for (int num : nums) {//nums数组每个数都右移i位
if (((num >> i) & 1) == 1) {//如果x的第i位是1
sum++;//此时sum表示nums数组所有数第i位数字之和
}
}
sum %= 3;//此时sum表示只出现一次的那个数第i位的数字
if (sum == 1) {
ret |= 1 << i;//将第i位修改为1
}
}
return ret;
}
}
消失的两个数字
思路:
1、假设nums数组长度为n-2,则包含了从1~n的所有整数(缺了两个数,假设为a,b)。所以,先将nums数组中所有数全部异或,继续将1~n的所有整数一起全部异或,根据异或定律,所得结果为a^b
2、由于a和b不相等,所以a^b肯定不为0,找出a^b最低位的那个1,将其记为diff位(假设a的diff位是0,b的diff位是1)。
由此,可以将nums中的数分为两类:
(1)diff位是0的数,统称为X1
(2)diff位是1的数统称为X2
3、先将X1全部异或一遍,再将X1和a一起全部异或一遍,所得最终结果为a
先将X2全部异或一遍,再将X2和b一起全部异或一遍,所得最终结果为b
由此求出消失的数字a和b
class Solution {
public int[] missingTwo(int[] nums) {
//将所有数异或在一起,假设消失的两个数字为a,b
int tmp = 0;
for (int x : nums) {
tmp ^= x;
}
for (int i = 1; i <= nums.length + 2; i++) {
tmp ^= i;
}
//此时tmp=a^b
//找出a,b中二进制最低位不同的那一位,也就是tmp中数字是1的那一位
int diff = 0;
while (true) {
if (((tmp >> diff) & 1) == 1) {//如果tmp的第diff位是1则结束循环
break;
}else{
diff++;
}
}
//根据diff位的不同,将所有数划分为两类来异或
//一类数第diff位是0,另一类数第diff位是1
int a = 0, b = 0;
for (int x : nums) {
if (((x >> diff) & 1) == 1) {//第diff位是1的数和b异或
b ^= x;
} else {//第diff位是0的数和a异或
a ^= x;
}
}
for (int i = 1; i <= nums.length + 2; i++) {
if (((i >> diff) & 1) == 1) {//第diff位是1的数和b异或
b ^= i;
} else {//第diff位是0的数和a异或
a ^= i;
}
}
return new int[]{a, b};
}
}