目录
- 前言
- 运算符
- 异或
- 题目(一)
- 解析
- 题目(二)
- 解析
前言
咱先唠唠嗑,上来就是知识点,多少有些枯燥无味了。当然,可以选择跳过。
话说为什么我们要去用位运算呢,这不是计算机该做的吗?存在即合理:
1.因为相同的运算,位运算实现比数学运算符实现速度要快,计算机底层就是二进制位运的算,而使用数学运算符,还要将其转换成位运算,这其中就存在一定的开销。虽然速度相差不大,但是当数据量越大时,形成的差距也会越大。搞算法嘛,无非就是想让程序尽可能快的解决问题。
2.位运算可以更方便的利用二进制。说这个可能有些伙伴会有点懵逼,没关系,稍后的讲解中会领略到位运算的奇妙。
运算符
Java中的位运算符:
1.位与:&
2.位或:|
3.位非:~
4.异或:^
5.左移:<<
6.右移:>>
7.无符号右移:>>>
当然还有与之对应的&=,|=等等
值得注意的是
1.无符号右移:>>>在c++中是不存在的,无符号右移左端直接补零,而普通的右移左端补符号位。
2.位运算只能给整数做运算,float和double是不行的。
3.对于btye,short,int这三种类型,i<<k等价于i<<(k%32),伙伴们可以去试试,而对于long类型而言,i<<k等价于i<<(k%64),这是java规定的。
常见操作
这里介绍一些常用的小技巧,可以稍微记一下。但是为了控制篇幅,这里就不进行过程推导了(其实也就4和5需要演算一下才好理解,其他都比较简单)
a<<1 a*2
a<<1|1 a*2+1
a>>1 a/2
a&((~a)+1) 只保留a的二进制中最低位的1
a&(a-1) 将a的二进制中最后一位1置零(可以用于判断a是否是2的n次方)
a>>b&1 得到a的二进制中右边第b+1位
a&1 判断奇偶,奇数为1,偶数为0
a^=b;b^=a;a^=b; 交换a,b。注意a和b不能指向同一个内存单元,比如对数组中的同一个元素自我交换
异或
在算法题中,用的最多的莫过于“异或”了,所以下文的重点就是异或运算符。
异或的运算规则
1^1=0
0^0=0
1^0=1
0^1=1
总的来说相同得0,不同得1
首先我们来看看异或的运算律:
1.归零律:a^a=0
2.交换律:a^b=b ^a
3.恒等律:a^0=a
4.结合律:a^b ^c=a ^(b ^c)=(a ^b) ^c
5.自反律:a^b ^a=b
6.d=a^b ^c 可以推出a=d ^b ^c
推导起来很简单,大家可以试一下。
上面讲了这么多,还是要实践一波才得劲呀。
题目(一)
题目:
给定一个数组,数组元素中仅存在一个数字出现了奇数次,其他数字均出现了偶数次,求这个出现了奇数次的数字。
例如:
a=[1,1,6,6,7,9,7]
很明显,所求答案为9,只有9出现了一次
不知道伙伴们有没有思路,如果没有思路的话,给点小提示:
异或的【结合律,归零律,恒等率】
解析
直接对数组的所有元素做异或运算便可得出答案
为什么呢?
举个栗子:a=[1,1,6,6,7,9,7]
1.首先结合律结合律:所有异或的顺序对结果是没有影响的。那么也就相当于依次异或[1,1,6,6,7,7,9]。
2.然后归零律:所以a^a=0。那么结果会变成[0,0,0,9],相等的归零了
3.最后恒等律:所以a^0=a。那么结果就变成了[9]
总结来说:出现偶数次的数字最后都会归零,只有奇数次的数字会剩下一个,那么最后的结果就是那个出现了奇数次数的数字了。
public static int getAlone(int[] nums){
int res=0;
for (int i : nums) {
res^=i;
}
return res;
}
题目(二)
有了上题的铺垫,是否骚微感觉到了位运算的一丝丝神奇呢?再来亿题助助兴吧,估计头发不保。再来一题吧,Come on。
题目
给定一个数组,数组元素中仅存在【两个】数字出现了奇数次,其他数字均出现了偶数次,求这两个出现了奇数次的数字。
ps:跟上一题有那么点类似
例如:
a=[1,1,2,2,9,5,6,6,5,4]
答案便是9和4,只有这哥俩出现了奇数次。
不知阁下有思路否,没思路看题解嘛。^_ ^。
ps:如果想了半天还没有思路的话,直接看题解嘛,何必为难自己。很多算法是那些大神花了几个月甚至几年才研究出来的,你要是去自己死磕,估计你的秀发很快就要离你而去了。
题解有点长,不过其实很简单。一定要耐心看完。
解析
首先假设所求的两个数分别为a和b
第一步:对所有数组元素做异或运算,的出来的结果就是temp=a^ b。
这点大家应该没问题吧。这里是上一题一样的思路,偶数归零了,最后就剩下a^ b了。
第二步:求出temp的二进制中最后一位1。
求这个有什么用呢?待我细细道来。
首先a和b是两个不同的数,异或运算是相同得0,不同得1。假设temp的二进制中第k位为1,就表示a和b的第k位二进制是不相同的。
那么我们就可以把数组中的数字分为两个部分:1.二进制第k位为1的数
2.二进制第k位为0的数而a和b会被分别划分进这两个不同的部分,为什么呢,因为我们做划分就是拿a和b不同之处做的划分,所以a和b一定不会在同一个部分中。
第三步:对其中的一部分的所有数字做异或运算。
既然我们将其划分为了两个部分,那对其中的一部分的所有数字做异或运算,就可以得出所求答案中的一个数字了,假设是a(也可以是b,这都不重要)
第四步:求出结果b
说到这里,大家肯定知道该怎么做了,直接对另一部分的所有数字做异或运算就可以得出b了。
当然没毛病啦,但是有个更简单的方法【自反律:a^ b^ a=b】。
既然上面求出来的temp=a^ b,那么b = a^ b^ a = temp^ a.
这样是不是更简单呢。
public static int[] getTwoAlone(int []nums){
int temp=0,a=0,k;
for (int i : nums) {
temp^=i;
}
k=temp&((~temp)+1);
for (int i : nums) {
if((i&k)!=0){
a^=i;
}
}
return new int[]{a,a^temp};
}
看完之后是不是觉得有手就行。