一、交换
不允许你使用额外的辅助变量来完成交换
我们知道,两个相同的数异或之后结果会等于 0,即 n ^ n = 0。并且任何数与 0 异或等于它本身,即 n ^ 0 = n。所以,解释如下:
把(1)中的 x 带入 (2)中的 x,有
y = x^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。
异或运算支持运算的交换律和结合律。
int swap() //交换
{
int x = 10,y = 20;
x = x^y; //交换
y = x^y;
x = x^y;
cout<<x<<" "<<y<<endl;
}
二、判断奇偶数
int Odd_even(int x) //判断奇偶数
{
if(x & 1)
{
cout<<"奇数"<<endl;
}
else{
cout<<"偶数"<<endl;
}
}
三、一组数据里面,只有一个数出现过奇数次,其他都是偶数次,请找出这个数
给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数
或者一组数据里面,只有一个数出现过奇数次,其他都是偶数次,请找出这个数
我们刚才说过,两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身,所以我们把这一组整型全部异或一下,例如这组数据是:1, 2, 3, 4, 5, 1, 2, 3, 4。其中 5 只出现了一次,其他都出现了两次,把他们全部异或一下,结果如下:
由于异或支持交换律和结合律,所以:
1^2^3^4^5^1^2^3^4 = (1^1)^(2^2)^(3^3)^(4^4)^5= 0^0^0^0^5 = 5。
也就是说,那些出现了两次的数异或之后会变成0,那个出现一次的数,和 0 异或之后就等于它本身。
//int a[] = {1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4};
int even_number(int *a,int len)
{
int tmp = a[0];
for(int i = 1;i<len;i++)
{
tmp = tmp ^ a[i];
}
cout<<tmp<<endl;
}
时间复杂度为 O(n),空间复杂度为 O(1),而且看起来很牛逼。
四、m的n次方
如果让你求解 m 的 n 次方,并且不能使用系统自带的 pow 函数。
//求m的n次方,就是快速幂算法
int pow(int m,int n)
{
int tmp = m;
int sum = 1;
while(n)
{
if(n & 1)
{
sum *=tmp;
}
tmp *=tmp;
n = n >> 1;
}
return sum;
}
如果是求2的n次方,不需要上面那样做,直接1<<n,一句话搞定;
五、找出不大于N的最大的2的幂指数
例如 N = 19,那么转换成二进制就是 00010011(这里为了方便,我采用8位的二进制来表示)。那么我们要找的数就是,把二进制中最左边的 1 保留,后面的 1 全部变为 0。即我们的目标数是 00010000。那么如何获得这个数呢?相应解法如下:
1、找到最左边的 1,然后把它右边的所有 0 变成 1
2、把得到的数值加 1,可以得到 00100000即 00011111 + 1 = 00100000。
3、把 得到的 00100000 向右移动一位,即可得到 00010000,即 00100000 >> 1 = 00010000。
那么问题来了,第一步中把最左边 1 中后面的 0 转化为 1 该怎么弄呢?我先给出代码再解释吧。下面这段代码就可以把最左边 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,如此往复下去….
法一、
//找出不大于N的最大的2的幂函数
//这个数就是最左边的1保留,后面的1全部变成0
//操作流程:
//最左边的1保留,后面的0全部变成1
//加1
//再右移一位
//假设是32位系统,int是4字节
int Find_N(int n)
{
n |= n>>1;
n |= n>>2;
n |= n>>4;
n |= n>>8;
n |= n>>16; //右边全部变成1
return (n+1)>>1;
}
这种做法的时间复杂度近似 O(1),重点是,高逼格。
法二、
//n逐位右移到0,记录右移次数m次,然后1左移m-1次
int Find_N2(int n)
{
int i = 0 ;
while(n)
{
n = n>>1;
i++;
}
return 1<<i-1;
}
总结
这里说一下,位运算很多情况下都是很二进制扯上关系的,所以我们要判断是否是否位运算,很多情况下都会把他们拆分成二进制,然后观察特性,或者就是利用与,或,异或的特性来观察,总之,我觉得多看一些例子,加上自己多动手,就比较容易上手了。所以呢,继续往下看,注意,先别看答案,先看看自己会不会做。
以上文字内容源于https://mp.weixin.qq.com/s/C6o6T9ju34vAxNBg5zobWw