一、找单身狗(初级.一个单身狗)
请看题:(读者可以读完题后可以先独立思考一会)
找出单身狗:
有一个整形数组,其中有一个数字是单独出现,其余数字都是成对出现的,请找出只出现一次的数字。
例如 找出 int arr[]={1,2,3,4,5,8,5,4,3,2,1} 中只出现一次的数字(单身狗)
结果:8
怎么找到这个8呢?
温馨提示:方法三为使用操作符(重点)的方法,看本文章需了解操作符基本知识,本文重在应用
方法一(易想):
解题步骤:
- 排序数组,让数组变有序 int arr[]={1,1,2,2,3,3,4,4,5,5,8}
- 然后依次以两个数字为单位,找到两个数字不同的那一组,前一个数字就是单身狗
//方法一
#include<stdio.h>
#include<stdlib.h>
int Compare(const void* elem1, const void* elem2)
{
int* e1 = (int*)elem1;
int* e2 = (int*)elem2;
if (*e1 > *e2)
return 1;
else if (e1 == e2)
return 0;
else
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,8,5,4,3,2,1 };
int num = sizeof(arr) / sizeof(arr[0]);
qsort(arr, num, sizeof(int), Compare);//使用快排排序
//arr[]={1,1,2,2,3,3,4,4,5,5,8}
//查找
int i = 0;
int j = arr[0];
int dog = 0;
//单身狗藏在其中
for (i = 1; i < num; i+=2)
{
if (j == arr[i])
{
j = arr[i + 1];
}
else
{
dog = j;
break;
}
}
//单身狗留在最后
if (i >= num)
{
dog = j;
}
printf("%d\n", dog);
}
方法二(易想):
解题步骤:
- 依次记录某个数字出现的次数,如果只出现一次那么这个数就是单身狗
//方法二
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,8,5,4,3,2,1 };
int num = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int j = 0;
int dog= arr[0];
int tag = 0;//记录出现次数
for (i = 0; i < num; i++)
{
dog = arr[i];
for (j = 0; j < num; j++)
{
if (arr[j] == dog)
{
tag++;
}
}
if (tag == 1)
{
break;//只出现一次就找到单身狗啦、结束循环
}
else
{
tag = 0;//重置次数
}
}
printf("%d\n", dog);
return 0;
}
方法三(重点):
上面两种方法虽然都能够解决问题,但是执行的速度不行
下面我们介绍一种非常牛的方法:
基本知识:
按位异或:即二进制位相同为零,相异为一
1. a^a=0 (相同的数字,每个二进制位都相同,结果为0)
2. 0^a=a ( 一个数与0异或,二进制位上0还是0,1还是1,大小不变)
3.按位异或支持交换律 (3^3^5=0^5=5 3^5^3=5)
复习了基本的知识后那我们就能很快想到解决的方法了:
解题步骤:
- 把数组中的所有的数字异或在一起,成对出现的数字异或结果为0,最后只剩下0^8,这样就能快速找到单独出现的数字
例如 1^2^3^4^5^8^5^4^3^2^1
= 1^1^2^2^3^3^4^4^5^5^8
= 0^8 = 8
代码实现:
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,8,5,4,3,2,1 };
int num = sizeof(arr) / sizeof(arr[0]);
int dog = 0;
int i = 0;
for (i = 0; i < num; i++)
{
dog ^= arr[i];//整个数组中的数异或
}
printf("%d ", dog);
return 0;
}
这样我们就能快速简单的找出单身狗了
二、找出单身狗(中级.两个单身狗)
请看题:
找出单身狗:
有一个整形数组,其中有两个数字是单独出现,其余数字都是成对出现的,请找出只出现一次的数字。
例如 找出 int arr[]={1,2,3,4,5,1,2,3,4,6} 中只出现一次的数字(单身狗),
结果:5,6
在这里方法一、方法二也同样适用,在这里就不再演示。
我们直接来看用操作符的方法:
我们发现若直接把数组中的所有数字直接异或,那么最终会得到5^6,无法得到我们想要的结果。
但我们知道既然是两个不同的数字,那么他们的二进制位中一定有某一位是不同的。
知道了这个有什么用呢?
大有用处:我们刚才已经说了5和6如果放在一起,那么最后什么也得不到,所以我们就要把这两个不同的数分开,把找两个单身狗问题简化为找一个单身狗问题,那么分开的依据就是这两个数的二进制位中有一位是不相同的,同时相同的数字一定会分到同一组。这样分别对分好的两组数字异或就能找出我们的想要的数字。
其次,我们也能很方便的找出这两个数字的二进制哪一位是不同的,根据相异为1,我们就看5^6的二进制中哪一位为1。
我们来举个例子:
1的二进制:001
2的二进制:010
3的二进制:011
4的二进制:100
5的二进制:101
6的二机制:110
将所有数异或在一起得到:5^6=011
我们发现结果中第一位为1,那么把第一位为1的异或在一起,就有:1^1^3^3^5=5
把第一位为0的异或在一起,就有:2^2^4^4^6=6
当然我们也可以用第二位来分类,与上面同理。
有些同学不清楚怎么找到哪一位为1:
我们来一起复习下:
a&1的结果有两个: 如果a的第一位为1那么结果为一,否则为0
那么我们再把移位符使用上,先判断第一位是否为1,如果不是就把这位移走, 直到有1出现
int pos = 0;
for (i = 0; i < 32; i++)
{
if (((result >> i) & 1) == 1)
{
pos = i;
break;
}
}
有了上面的知识,最后就是手到擒来、水到渠成啊:
解题步骤:
- 首先把所有数异或在一起,结果为 result
- 计算 result 中哪一位为1(也就是找我们分成两组的依据)
- 分组同时异或
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
int num = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int result = 0;
//1.首先把所有数异或在一起,结果为 result
for (i = 0; i < num; i++)
{
result ^= arr[i];
}
//2.计算result中哪一位为1
int pos = 0;
for (i = 0; i < 32; i++)
{
if (((result >> i) & 1) == 1)
{
pos = i;
break;
}
}
//3.分组并且异或在一起
int dog1 = 0;
int dog2 = 0;
for (i = 0; i < num; i++)
{
if (((arr[i] >> pos) & 1) == 1)
{
dog1 ^= arr[i];
}
else
{
dog2 ^= arr[i];
}
}
printf("%d %d",dog1,dog2);
return 0;
}
恭喜你!!!能找出单身狗了!!!
看到这,不知道你对操作符的了解是否更进一步了呢?作者建议大家反复观看,加深了解。
欢迎关注作者,作者会持续更新有趣的小问题,在有趣的玩意儿中成长!