操作符经典例题详解
目录
1、始终专一(没有小三)
2、单身狗
3、整数二进制1的个数(判断2的次幂)
4、找不同
5、拆分打印二进制序列
1、始终专一(没有小三)
题目:不能创建临时变量(第三个变量),实现两个数的交换。
要完成这个变态的题目,我们需要使用按位异或(^),这一个操作符,其含义是:将两个整数的二进制序列进行对照,对应位相同则为0,对应位相反则为1。
让我们先从简单的例子入手进行探究:
10 = 1010
7 = 0111
//10^7 = 1101 = 13
10^7^10 = 13^10 = 0111 = 7
//10^10 = 0000 = 0
10^10^7 = 0^7 = 7
10^7^10 = 10^10^7 = 7
由此我们可以发现:
1、单纯的按位异或操作符的运算符合交换律。
2、一个整数与另外一个整数做偶数次的按位异或运算,其结果值与它本身相同。
这意味着我们可以对两个变量不断进行按位异或运算,使其效果抵消来达到互换值的结果。实现代码如下:
int main()
{
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;//b=(a^b)^b=a^(b^b)=a
a = a ^ b;//a=(a^b)^a=(a^a)^b=b
printf("a=%d b=%d", a, b);
return 0;
}
注意:
使用这种方法来交换变量值有局限之处:
1、操作数只能是整型
2、代码可读性差
3、执行效率低
2、单身狗
题目:在一个整型数组中,只有一个数字出现一次,其他数组都是成对出现的,请找出那个只出现一次的数字。
例如:
数组中有:1 2 3 4 5 1 2 3 4,只有5出现一次,其他数字都出现2次,找出5。
要完成这个题目,我们要先创建一个数组,然后将这些数值放入这个数组之中。接着我们可能会想到对数组中的元素进行遍历,如果该元素出现了2次,则继续下一个,直到找到只出现一次的数据。
这种方法固然可行,但是具有需要使用两层循环,效率比较低的缺陷。
让我们换一个角度来看这个题目,所谓“单身狗”就是该数值在数组中只出现一次,而其他数值则出现两次,两次也属于偶数次,那我们只要对数组中的元素都进行按位异或运算,就可以保证最后得出的结果与“单身狗”相同。实现代码如下:
#include <stdio.h>
int find_single_dog(int arr[], int sz)
{
int ret = 0;
int i = 0;
for (i = 0; i < sz; i++)
{
ret ^= arr[i];
}
return ret;
}
int main()
{
int arr[] = { 1,2,3,4,5,1,2,3,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
int dog = find_single_dog(arr, sz);
printf("%d\n", dog);
return 0;
}
3、整数二进制1的个数(判断2的次幂)
题目:求⼀个整数存储在内存中的⼆进制中1的个数。
首先,我们最容易想到利用短除法的余数进行if判断并进行计数,实现代码如下:
#include <stdio.h>
int main()
{
int num = 10;
int count= 0;//计数
while(num)
{
if(num%2 == 1)
count++;
num = num/2;
}
printf("⼆进制中1的个数 = %d\n", count);
return 0;
}
这个方法可以实现目的,但是,方法还可以继续优化吗?
接着,我们或许可以想到可以通过遍历对一个占32位的整型的每一位进行判断并计数,实现代码如下:
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
for(i=0; i<32; i++)
{
if( num & (1 << i) )
count++;
}
printf("⼆进制中1的个数 = %d\n",count);
return 0;
}
但是这种方法任需要进行32次判断,能不能再进行优化呢?
如果能够记下距离左或右端最近的一位1后将其销毁或变为0,就可以通过重复这种方法定向快速地统计出1的个数。让我们先看一下实现代码:
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while(num)
{
count++;
num = num&(num-1);
}
printf("⼆进制中1的个数 = %d\n",count);
return 0;
}
要点:
1、num作为while的循环条件:num只要二进制序列中还有1,值就不会为0,判断结果为真,顺利进入循环;num中全为0,值就为0,判断结果为假,不进入循环。
2、num = num&(num-1) :按位与运算(&)规则:将两个整数的二进制序列进行对照,对应位有0则为0,对应位同时为1才为1。
我们该如何理解这个表达式呢?让我们从简单的例子入手:
10 = 1010
10 - 1 = 9 = 1001
10&9 = 1000 = 8
对照一下10与10^9的二进制序列,我们发现最右端的1消失了。
这是巧合吗?其实并不然。
假设num的二进制序列中的最低位为1:
num-1后最低位变为0,其他位不发生改变。
做按位与运算后,最后一位结果为0,其他位结果不变。
假设num的二进制序列中的最低位为0:
num-1后最低为退位变为1,此时最靠近右端的1变为0,该位与最低位之间的0全部变为1。
再进行按位与运算,最靠近右端的1所占的位之前位结果不变,最靠近右端的1所占的位结果为0,该位与最低位之间的0所占位结果为0,最低位结果为0。
由此可知,num = num&(num-1),这一表达式可以让二进制序列中最右端的1消失,达成我们的目的。
判断2的次幂
如果要判断一个数是不是2的次幂,只要判断其二进制序列中是不是只有一个1就可以了,实现代码如下:
int main()
{
int n = 0;
scanf("%d", &n);
if ((n & (n - 1)) == 0)
{
printf("yes\n");
}
else
{
printf("no\n");
}
return 0;
4、找不同
题目:求两个数二进制中不同位的个数
编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?
输入例子:1999 2299
输出例子:7
这个题目现在看来就比较简单了,我们可以通过按位异或运算来得到这两个数二进制位序列中哪些位是相同的(结果为0),哪些位是不同的(结果为1),随后再使用按位与运算统计有几个1,输出即可,实现代码如下:
#include <stdio.h>
int calc_diff_bit(int m, int n)
{
int tmp = m^n;
int count = 0;
while(tmp)
{
tmp = tmp&(tmp-1);
count++;
}
return count;
}
int main()
{
int m,n;
while(scanf("%d %d", &m, &n) == 2)
{
printf("%d\n", calc_diff_bit(m, n));
}
return 0;
}
5、拆分打印二进制序列
题目:获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列。
思路:
- 提取所有的奇数位,如果该位是1,输出1,是0则输出0
- 以同样的方式提取偶数位置
检测num中某一位是0还是1的方式:
- 将num向右移动i位
- 将移完位之后的结果与1按位与,如果:
结果是0,则第i个比特位是0
结果是非0,则第i个比特位是1
void Printbit(int num)
{
for(int i=31; i>=1; i-=2)
{
printf("%d ", (num>>i)&1);
}
printf("\n");
for(int i=30; i>=0; i-=2)
{
printf("%d ", (num>>i)&1);
}
printf("\n");
}