位运算在算法中的运用

目录

1. 逻辑运算符与位运算符

2. 位运算算法基础

判断奇偶数

交换两个数

找出没有重复的数

2 的 n 次方

找出不大于 N 的最大的 2 的幂指数


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).

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值