Java基础之位运算
前言
- 位运算最大的好处是可以提高程序性能
- 正数的反码还是等于原码,负数的反码就是他的原码除符号位外,按位取反
- 正数的补码等于他的原码,负数的补码等于反码+1
- 负数的二进制采用补码,比如-1 原码:1000 0001,反码:1111 1110,补码:1111 1111
- Java 中 int 数据类型是32位、有符号的以二进制补码表示的整数
基础
1. 按位与 (&)
-
规则:两个操作数对应位——同1为1,其余为0
-
技巧1:等价于一个数对 2 ^ n 取模(求余数)—— 取模运算转化成位运算 (在不产生溢出的情况下)
- a & 0 等价于 a % 1
- a & 1 等价于 a % 2
- a & 3 等价于 a % 4
- a & 7 等价于 a % 8
- a & (2 ^ n - 1) 等价于 a % (2 ^ n )
- 示例
public static void main(String[] args) { int a=23; int b=7; System.out.println(a & b); } 输出结果:7
-
技巧2:判断一个数是偶数还是奇数
- a & 1==0,a为偶数
- a & 1==1,a为奇数
-
技巧3:用于消去一个数转化为二进制数后的最后一个1
- 示例
x & (x-1) x = 1100 x-1 = 1011 x & (x-1) = 1000
- 应用1:判断一个大于0的整数是不是2的幂
- x & (x-1) == 0是2的幂,如果是2的幂,那一定只有1个1,消去这个1,变为0
- 否则不是
- 应用2:计算在一个 32 位的整数的二进制表示中有多少个 1
- 由 x & (x-1) 消去x最后一位知。循环使用x & (x-1)消去最后一位1,计算总共消去了多少次即可
- 应用3:将整数A转换为B,需要改变多少个bit位
- 思考将整数A转换为B,如果A和B在第i(0<=i<32)个位上相等,则不需要改变这个BIT位,如果在第i位上不相等,则需要改变这个BIT位。所以问题转化为了A和B有多少个BIT位不相同。联想到位运算有一个异或操作,相同为0,相异为1,所以问题转变成了计算A异或B之后这个数中1的个数
-
技巧4:取低位8bit,高位清0
x & 255
-
技巧5:取x的奇数位并将偶数位用0填充
x & 0xAAAA
-
技巧6:取x的偶数位并将奇数位用0填充
x & 0x5555
2. 按位或 (|)
- 规则:有1为1,全0为0
- 技巧:
3. 按位取反 (~)
-
规则:~是一元操作符,将1变0,0变1
-
技巧:一个数的相反数 = 对一个数进行取反加1
-
示例
public static void main(String[] args) { int a=123; System.out.println(~a+1); } 输出:-123
4.按位异或 (^)
-
规则:相同为0,否则为1
- 异或的计算表
操作数1 操作数2 按位异或 0 0 0 0 1 1 1 0 1 1 1 0 - 规律:异或0具有保持的特点,而异或1具有翻转的特点
- 异或运算的含义是二进制下不考虑进位的加法
- 计算规律:
- a ^ a =0
- a ^ 0 =a
- a ^ b =b ^ a
- a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c
- d = a ^ b ^ c 可以推出 a = d ^ b ^ c
- a ^ b ^ a = b
-
技巧1:不用temp交换2个整数
public static void main(String[] args) { int a=15; int b=2; a ^= b; //a = a ^ b; b ^= a; //b = b ^ a; --> b = b ^ (a ^ b) --> b = a a ^= b; //a = a ^ b; --> a = a ^ (b ^ a) --> a = b System.out.println(a+"-"+b); } 输出:2 - 15
-
技巧2:翻转操作,初始值为1,操作一次变为0,再操作一次变为1:
num = 1^num //num为原始值
-
技巧3:给定一个数组,找出其中只出现一次的那个数
- 示例1:Given [1,2,2,1,3,4,3], return 4
- 数组中,只有一个数出现一次,剩下都出现两次
- 解题思路:因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或一遍,就可以得到唯一的那个数
- 代码
public static void main(String[] args) { int a[]={1,2,2,1,3,4,3}; int ans=0; for(int i=0;i<7;i++){ ans^=a[i]; } System.out.println("ans="+ans); } 输出:ans=4
- 示例2:Given [1,1,2,3,3,3,2,2,4,1] return 4
- 数组中,只有一个数出现一次,剩下都出现三次
- 解题思路:自定义某种三进制运算符 #,使 0 # 1 = 1,1 # 1 = 2,2 # 1 = 0
1 |0 0 0 1 1 |0 0 0 1 2 |0 0 1 0 3 |0 0 1 1 3 |0 0 1 1 3 |0 0 1 1 2 |0 0 1 0 2 |0 0 1 0 4 |0 1 0 0 1 |0 0 0 1 看最右边一列:1101110001,有6个1 看中间一列:0011111100,有6个1 看右数第三列:0000000010,有1个1 我们只需要把1的个数是3的倍数的列写成0,不是3的倍数写成1 最后写出:0100 (4) 原因:如果所有数字都出现了 3 次,那么每一列的 1 的个数就一定是 3 的倍数。 One+ = (One ^ B) & (~Two) Two+ = (~One+) & (Two ^ B)
- 代码
class Solution { public int singleNumber(int[] nums) { int one = 0, two = 0, temp = 0; for (int num : nums) { temp = (two & num) | (one & ~num); two = (~one & ~two & num) | (two & ~num); one = temp; } return two; // 这里return two的原因是第二个状态为 01,one 为 0,two 为 1 } }
- 参考链接:Leetcode 137 Single Number II(模拟三进制法,图表解析)
- 示例3:Given [1,2,2,3,4,4,5,3] return 1 and 5
- 数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的
- 解题思路:有了第一题的基本的思路,我们不妨假设出现一个的两个元素是x,y,那么最终所有的元素异或的结果就是res = x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1,那么这一位1对于这两个数x,y只有一个数的该位置是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。求出x,y其中一个,我们就能求出两个了
- 代码
5.左移 (<<)
- 规则:将一个数的各二进制位全部左移 n 位,右端低位补0
- 示例1:9 << 2 = 36 ----> 0000 1001(9) ----> 0010 0100(36)
- 示例2:-6 << 2 = -24 ----> 1111 1010 (-6) ----> 1110 1000 (-24)
- 技巧1:乘法运算转化成位运算 (在不产生溢出的情况下) a * (2^n) 等价于 a<< n
- java中int所能表示的最大数值是31位,加上符号位共32位
- 任何数左移(右移)32的倍数位等于该数本身
- 在位移运算m<<n的计算中,若n为正数,则实际移动的位数为n%32,若n为负数,则实际移动的位数为(32+n%32),右移,同理
6.右移 (>>)
- 规则:将一个数的各二进制位右移N位,移到右端的低位被舍弃
- 如果该数为正,则高位补0
- 如果该数为负,则高位补1
- 示例1:15 >> 2 = 3 ----> 0000 1111 (15) ----> 0000 0011 (3)
- 示例2:-6 >> 2 = -2 ----> 1111 1010 (-6) ----> 1111 1110 (-2)
- 技巧1:除法运算转化成位运算 (在不产生溢出的情况下) a / (2^n) 等价于 a>> n
7.无符号右移 (>>>)
- 规则:低位溢出,高位补0。注意,无符号右移(>>>)中的符号位(最高位)也跟着变,无符号的意思是将符号位当作数字位看待
- 示例:-1>>>1,结果为2147483647,即int类型所能表示的最大整数
- 其他得到 Integer.MAX_VALUE 的方法
- ~(1 << 31)
- (1 << -1)-1
- ~(1 << -1)
综合运用
-
求两数的平均值
public static void main(String[] args) { int a=3; int b=7; System.out.println((a&b)+((a^b)>>1)); } 输出:5
-
求一数的绝对值
public static void main(String[] args) { int a=-15; int b=a>>31; System.out.println((a^b)-b); } 输出:15
-
改变开关量的状态
public static void main(String[] args) { int x=1; if (x==0) x=1; else x=0; System.out.println(x); } /***************等价于******************/ public static void main(String[] args) { int x=1; x=0^1^x; System.out.println(x); }
-
对int型变量a转化为2进制数后的第k位进行操作
- 取出第k位,即
a>>k&1
- 第k位清0,即
a=a&~(1<<k)
- 第k位置1,即
a=a|(1<<k)
- 取出第k位,即
-
对int型变量a循环左移k次或右移k次
- 左移k次,即
a=a<<k|a>>16-k
- 右移k次,即
a=a>>k|a<<16-k
- 左移k次,即
-
使用二进制进行子集枚举
- 应用:给定一个含不同整数的集合,返回其所有的子集
- 示例:如果 S = [1,2,3],有如下的解:[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2] ]
- 思路:使用一个正整数二进制表示的第i位是1还是0,代表集合的第i个数取或者不取
- 所以从0到2n-1总共2n个整数,正好对应集合的2^n个子集
S = {1,2,3} N bit Combination 0 000 {} 1 001 {1} 2 010 {2} 3 011 {1,2} 4 100 {3} 5 101 {1,3} 6 110 {2,3} 7 111 {1,2,3}