目录
1. 逻辑运算符与位运算符
运算符 | 说明 | 示例 | |
---|---|---|---|
逻辑运算符 | && 与 | 短路与,相当于数学集合中的求交集。 &&连接项均为true时,结果为true; 否则为false. 从&&连接的第一个表达式的值开始依次计算,若某一项为false时,结果即为false,不会再继续计算值为false项后面的各项表达式的值。例如对于if(str != null && !str.equals("")) 则不会抛出NullPointerException异常; 若把 && 改成 & ,则会抛出NullPointerException异常。 | boolean a = 1 > 0; // true boolean b = 2 < 3; // false String c = 4 > 5; // false boolean calc = a && b && c; int num = 1; boolean calc = (a && ++num > 1); // false,num不会增长 |
|| 或 | 短路或,相当于数学集合中的求并集。 || 连接项中只要有一项的值为true,则结果为true,只有当所有连接项值均为false时,结果为false。 从 || 连接的第一个表达式的值开始依次计算,若某一项为true时,结果即为true,不会再继续计算值为true项后面的各项表达式的值。 | boolean a = 1 > 0; // true boolean b = 2 < 3; // false boolean calc = a || b; // true // 当判断到 a 为 true 时,则calc结果即为true,并未继续判断 b 的值。 | |
! 非 | 相当于数学集合中的求补集。 | boolean a = true; boolean calc = !a; // false boolean calc1 = !!a; // true | |
位运算符 | & 与 | 有false则结果为false;
二进制表达:全部为1结果则为1,否则结果为0. 即:0&0=0;0&1=0;1&0=0;1&1=1 | boolean a = true; boolean b = false; boolean c = a & b; // false int num = 1; boolean calc = (a & ++num > 1); // 值为true,num会增长
二进制举例:100101 & 110011 = 100001 十进制转二进制进行位运算(位数不足的前面补0): 3 & 5 = 011 & 101 = 001 = 1;
|
| 或 | 有true则结果为true;
二进制表达:有1则结果为1,否则结果为0. 即:0|0=0;0|1=1;1|0=1;1|1=1. | boolean a = true; boolean b = false; boolean c = false; boolean d = a | b | c; // true
二进制举例:100101 & 110011 = 110111 十进制转二进制进行位运算(位数不足的前面补0): 3 | 5 = 011 | 101 = 111 = 7;
| |
~ 非 | 非运算即取反运算,在二进制中1变0, 0变1. | 100101进行位非运算后的结果为 011010,即 11010 | |
^ 异或 | 相同则为false,不同则为true;(同假异真)
二进制表达:相同结果为0,不同结果为1. | boolean a = true; boolean b = false; boolean c = a ^ b; // false
二进制举例: 100101 ^ 110011 = 010110
| |
>> 右移 | 带符号右移">>"。即将操作数的各二进制位全部右移指定的位数。右移时,操作数移出右边界的位被屏蔽,左边开始用符号位填补空位,符号位为1则填补1,符号位为0,则填补0(即负数高位补1,正数高位补0)。 不带符号右移 ">>>". 操作数的各二进制位全部右移指定的位数。右移时,操作数移出右边界的位被屏蔽,左边开始用0填补空位。 | 以下以8位二进制数为例:
9 >> 2 = 0000 1001 >> 2 = 0000 0010 = 2 (9右移2位,其实就是除以2的平方,即 9/(2x2) = 2 ) 9 >>> 2 = 0000 1001 >>> 2 = 0000 0010 = 2
-9 >> 2 = 1111 0111(补码) >> 2 = 1111 1101 = -3 | |
<< 左移 | 将操作数的各二进制位全部左移指定的位数。左移时,操作数移出左边界的位被屏蔽,右边开始用0填补空位. | 以下以8位二进制数为例:
9 << 2 = 0000 1001 << 2 = 0010 0100 = 36 (9左移2位,其实就是乘以2的平方,即 9x(2x2) = 36 )
-9 << 2 = 1111 0111(补码) << 2 = 1101 1100 = -36 |
2. 位运算算法基础
判断奇偶数
常规会用该数与2取模,然后判断结果;即 n % 2 ,结果为 0 则 n 为偶数,否则为奇数。
if(n % 2 == 0) {
// 偶数
} else {
// 奇数
}
位运算解法:如果把 n 以二进制的形式展示的话,只需判断最后一个二进制位是 1 (奇数) 还是 0 (偶数).
if ( n & 1 == 1) {
// n是奇数
} else {
// n是偶数
}
交换两个数
常规会使用一个额外变量来辅助交换,例如:交换 x 与 y 的值
int tmp = x;
x = y;
y = tmp;
若不允许用额外的辅助变量来完成交换!代码如下:
x = x + y;
y = x - y;
x = x - y;
或使用位运算:
x = x ^ y; // (1)
y = x ^ y; // (2)
x = x ^ y; // (3)
/**
* 把(1)中的 x 值带入(2)中,则 y = (x ^ y) ^ y = x ^ (y ^ y) = x ^ 0 = x , 即把 x 的值成功的赋给了 y.
* 对于(3),推导如下:x = x ^ y = (x ^ y) ^ x = (x ^ x) ^ y = 0 ^ y = y,即把y的值成功的赋给了x.
*/
已知异或运算的法则为:同假异真;二进制位计算则相同为0,不同为1。
则两个相同的数异或之后结果等于0,即 n ^ n = 0. 且任何数与0异或等于它本身,即 n ^ 0 = n 。
异或运算支持运算的交换律和结合律。
找出没有重复的数
问题:给出一组整型数据,这些数据中,其中有一个数只出现了一次,其他数都出现了两次,请找出只出现了一次的那个数。
常规解法:用一个哈希表来存储,每次存储的时候,记录某个数出现的次数,最后遍历哈希表,找出次数为1的那个数。此方法的时间复杂度为 O(n),空间复杂度为 O(n).
位运算解法:已知 n ^ n = 0, n ^ 0 = n. 所以只需要把这组数全部进行异或运算,结果即为只出现了一次的数。
例如:有一组数为 1,2,3 ,4 ,5 ,4 ,3 ,2 ,1。其中只有5只出现了一次,其他数出现了两次,把他们全部异或一下,结果如下:
由于异或遵循交换律和结合律,则:1^2^3^4^5^4^3^2^1 = (1^1)^(2^2)^(3^3)^(4^4)^5 = 0^0^0^0^5 = 5.
出现了两次的数异或了之后变为0,那个只出现了一次的数和0异或后等于它本身。
int find(int[] arr) {
int tmp = arr[0];
for(int i=1; i<arr.length; i++) {
tmp = tmp ^ arr[i];
}
return tmp;
}
2 的 n 次方
问题:求解 2 的 n 次方,且不能使用系统自带的 math 相关函数。
常规解法:连续让n个2相乘,时间复杂度 O(n), 代码如下:
int pow(int n) {
int tmp = 1;
for(int i=1; i<=n; i++) {
tmp = tmp * 2;
}
return tmp;
}
位运算解法:可以通过 & 1 和 >> 1 来逐位读取概述的二进制值,为 1 时将该位代表的乘数累乘到最终结果,时间复杂度O(logn)。代码如下:
int pow(int n) {
int sum = 1;
int tmp = 2;
while(n != 0) {
if(n & 1 == 1) {
sum *= tmp;
}
tmp *= tmp;
n = n >> 1;
}
return sum;
}
注:位运算很多情况下都是和二进制扯上关系的,所以我们要判断是否使用位运算,很多情况下都会把他们拆分成二进制,然后观察特性,或者就是利用与,或,异或的特性来观察。
找出不大于 N 的最大的 2 的幂指数
传统做法就是从 2 的最小次幂开始,不断乘以 2,并判断该值是否大于 N, 若大于了,则上一个2的幂次方则为结果值,时间复杂度 O(logn),代码如下:
int findMax(int N){
int sum = 1;
while(true) {
if(sum * 2 > N)
return sum;
sum = sum * 2;
}
}
二进制解法:将 N 转换为二进制数后,由二进制数的定义可知,保留该数最高位(最左边)的 1 , 其余全部变为 0 , 即为解。例如 N = 19,转换为二进制(假设为8位)则为 0001 0011,那么不大于该数的最大的2的幂指数为 00010000,那么怎么由 0001 0011 变为 0001 0000 呢,相应解法如下:
1、找到最左边的1,然后把它右边的所有0变成1: 0001 0011 -> 0001 1111
2、把得到的数加1 : 0001 1111 + 1 = 0010 0000
3、把得到的数右移一位: 0010 0000 >>1 = 0001 0000.
下面这段代码就可以把左边 1 后面的 0 全部转化为 1
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
就是通过把 n 右移并且做或运算即可得到。假设最左边的 1 处于二进制位中的第 k 位(从左往右数),那么把 n 右移一位之后,得到的结果中第 k+1 位也必定为 1,然后把 n 与右移后的结果做或运算,那么得到的结果中第 k 和 第 k + 1 位必定是 1;同样的道理,再次把 n 右移两位,那么得到的结果中第 k+2和第 k+3 位必定是 1,然后再次做或运算,那么就能得到第 k, k+1, k+2, k+3 都是 1,如此往复下去….
最终代码如下:
int findMax(int n) {
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8; // 整型一般是32位,以上述中假设的8位为例
return (n + 1) >> 1;
}
该做法时间复杂度近似等于 O(1).