private static void swap(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
复制代码
先来解释b 为何会变成 a。算法
首先,异或运算是符合交换律和结合律的,就是说 a ^ b ^ c 等于 a ^ c ^ b 等于 (a ^ b) ^ c。编程
因此,第二条语句就能够等价为 : b = a ^ b ^ b; ,由于(b ^ b) = 0 ,因此 b = a ^ 0 = a。数组
再来解释 a 为何会变成 b。ui
结合以上说法,当运行到 第三行代码时,a = a ^ b 而 b = a,因此 a = a ^ b = a ^ b ^ a。spa
同上,结合异或运算的交换律, a = (a ^ a) ^ b = b。code
其实,只要是互逆运算就能够进行这种不用临时变量的交换运算,如 加减法,乘除法。教程
public static void swap(int a,int b) {
a = a + b;
b = a - b;
a = a - b;
}
复制代码
public static void swap(int a,int b) {
a = a * b;
b = a / b;
a = a / b;
}
复制代码
public static int plus(int a, int b) {
while (b != 0)
{
int _a = a ^ b;
int _b = (a & b) << 1;
a = _a;
b = _b;
}
return a;
}
复制代码
主要利用异或运算来完成,异或运算有一个别名叫作:不进位加法。ip
那么a ^ b就是a和b相加以后,该进位的地方不进位的结果,相信这一点你们没有疑问,可是须要注意的是,这个加法是在二进制下完成的加法。get
而后下面考虑哪些地方要进位?it
什么地方须要进位呢? 天然是a和b里都是1的地方
a & b就是a和b里都是1的那些位置,那么这些位置左边都应该有一个进位1,a & b << 1 就是进位的数值(a & b的结果全部左移一位)。
那么咱们把不进位的结果和进位的结果加起来,就是实际中a + b的和。
a + b = (a ^ b) + (a & b << 1)
令:
a' = a ^ b, b' = (a & b) << 1 => a + b = (a ^ b) + (a & b << 1) = a' + b'
而后反复迭代,这个过程是在二进制下模拟加法的运算过程,进位不可能一直持续,因此b最终会变为0,也就是没有须要进位的了,所以重复作上述操做就能够 最终求得a + b的值。
N若是是2的幂次,则N知足两个条件。
N > 0
N的二进制表示中只有一个1, 注意只能有1个。
由于N的二进制表示中只有一个1,因此使用N & (N - 1)将N惟一的一个1消去,应该返回0。
bool checkPowerOf2(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
复制代码
x & (x - 1)消去x最后一位的1。
由x & (x - 1)消去x最后一位的1可知。不断使用 x & (x - 1) 消去x最后一位的1,计算总共消去了多少次便可。
public int countOnes(int num) {
int count = 0;
while (num != 0) // 不能是 > 0,由于要考虑负数
{
num = num & (num - 1);
count++;
}
return count;
}
复制代码
这个应用是上面一个应用的拓展
思考将整数A转换为B,若是A和B在第i(0 <=i < 32)个位上相等,则不须要改变这个BIT位,若是在第i位上不相等,则须要改变这个BIT位。
因此问题转化为了A和B有多少个BIT位不相同!
联想到位运算有一个异或操做,相同为0,相异为1,因此问题转变成了计算A异或B以后这个数中1的个数!
public int countOnes(int num) {
int count = 0;
while (num != 0) // 不能是 > 0,由于要考虑负数
{
num = num & (num - 1);
count++;
}
return count;
}
public int bitSwapRequired(int a, int b) {
return countOnes(a ^ b);
}
复制代码
思路就是使用一个正整数二进制表示的第i位是1仍是0来表明集合的第i个数取或者不取。 因此从0到2^n-1总共2^n个整数,正好对应集合的2^n个子集。以下是就是 整数 <=> 二进制 <=> 对应集合 之间的转换关系。
public List<List<Integer>> bitSubsets(int[] nums)
{
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < (1 << n); ++i) {
List<Integer> subset = new ArrayList<>();
for (int j = 0; j < n; j++) {
if((1 << j & i) != 0) {
subset.add(nums[j]);
}
}
list.add(subset);
}
return list;
}
复制代码
由于只有一个数刚好出现一个,剩下的都出现过两次,因此只要将全部的数异或起来,就能够获得惟一的那个数,由于相同的数出现的两次,异或两次等价于没有任何操做!
public int singleNumber(int[] nums) {
int result = 0, n = nums.length;
for (int i = 0; i < n; i++)
{
result ^= nums[i];
}
return result;
}
复制代码
由于其余数是出现三次的,也就是说,对于每个二进制位,若是只出现一次的数在该二进制位为1,那么这个二进制位在所有数字中出现次数没法被3整除。
对于每一位,咱们让Two,One表示当前位的状态。
咱们看Two和One里面的每一位的定义,对于ith(表示第i位):
若是Two里面ith是1,则ith当前为止出现1的次数模3的结果是2
若是One里面ith是1,则ith目前为止出现1的次数模3的结果是1
注意Two和One里面不可能ith同时为1,由于这样就是3次,每出现3次咱们就能够抹去(消去)。那么最后One里面存储的就是每一位模3是1的那些位,综合起来One也就是最后咱们要的结果。
若是B表示输入数字的对应位,Two+和One+表示更新后的状态
那么新来的一个数B,此时跟原来出现1次的位作一个异或运算,&上~Two的结果(也就是否是出现2次的),那么剩余的就是当前状态是1的结果。
同理Two ^ B (2次加1次是3次,也就是Two里面ith是1,B里面ith也是1,那么ith应该是出现了3次,此时就能够消去,设置为0),咱们至关于会消去出现3次的位。
可是Two ^ B也多是ith上Two是0,B的ith是1,这样Two里面就混入了模3是1的那些位!!!怎么办?咱们得消去这些!咱们只须要保留不是出现模3余1的那些位ith,而One是刚好保留了那些模3余1次数的位,`取反不就是否是模3余1的那些位ith么?最终对(~One+)取一个&便可。
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for(int i = 0; i < nums.length; i++)
{
ones = (ones ^ nums[i]) & ~twos;
twos = (twos ^ nums[i]) & ~ones;
}
return ones;
}
复制代码
有了第一题的基本的思路,咱们能够将数组分红两个部分,每一个部分里只有一个元素出现一次,其他元素都出现两次。那么使用这种方法就能够找出这两个元素了。不妨假设出现一个的两个元素是x,y,那么最终全部的元素异或的结果就是等价于++res = x^y++。
++而且res!=0++
为何呢? 若是res 等于0,则说明x和y相等了!!!!
由于res不等于0,那么咱们能够必定能够找出res二进制表示中的某一位是1。
对于x和y,必定是其中一个这一位是1,另外一个这一位不是1!!!细细琢磨, 由于若是都是0或者都是1,怎么可能异或出1
对于原来的数组,咱们能够根据这个位置是否是1就能够将数组分红两个部分。++x,y必定在不一样的两个子集中。++
并且对于其余成对出现的元素,要么都在x所在的那个集合,要么在y所在的那个集合。对于这两个集合咱们分别求出单个出现的x 和 单个出现的y便可。
public int[] singleNumber(int[] nums) {
//用于记录,区分“两个”数组
int diff = 0;
for(int i = 0; i < nums.length; i ++)
{
diff ^= nums[i];
}
//取最后一位1
//先介绍一下原码,反码和补码
//原码,就是其二进制表示(注意,有一位符号位)
//反码,正数的反码就是原码,负数的反码是符号位不变,其他位取反
//补码,正数的补码就是原码,负数的补码是反码+1
//在机器中都是采用补码形式存
//diff & (-diff)就是取diff的最后一位1的位置
diff &= -diff;
int[] rets = {0, 0};
for(int i = 0; i < nums.length; i ++)
{
//分属两个“不一样”的数组
if ((nums[i] & diff) == 0)
{
rets[0] ^= nums[i];
}
else
{
rets[1] ^= nums[i];
}
}
return rets;
}
复制代码
位运算的妙用到这就告一段落了,以上内容有一大部分是来自 九章算法位运算入门教程 , 在 LintCode 上还有相应的编程练习,去练习一下会得到更好的效果。