也是关于数组中数字出现的次数,但只有一个不同的数. 关于这个问题有很多的解决方法,可以创建两个数组,或用指针等方法来做,但这些方法的时间复杂度都不理想。此时就要发挥异或的作用了,对于这个位运算符,最开始有所使用是在交换两个数的时候(要求不借助第3个变量,而且保证在int的范围内),当时看到这个方法后,直呼秒啊。对于这个问题,异或又一次发挥了妙用。
#include
int main()
{
int a = 2;
int b = 3;
a = a^b;
b = a^b;
a = a^b;
printf("%d%d", a, b); //此时只需3次异或,就可以交换两个数,而且不用借助第3个变量,同时也可以保证在int的范围内
}
对于这个问题的前身,我们也可以使用异或运算.将数组中的所有元素异或即可找出那个不同的数字,但这时有一个重要的前提,也就是,那些重复的数字必须重复偶数次,因为也只有重复偶数次再与那个不同的数字异或时,结果才能是那个不同的数字.
下面举一个反列
#include
int main()
{
int a[4] = {
1, 1, 1, 4 };
printf("%d", a[0] ^ a[1] ^ a[2] ^ a[3]);
}
此时会发现结果并不是我们想要的,原因就是因为那个重复的数字并没有重复偶数次,而是重复了奇数次.
这时我们再去看剑指offer56题的时候,就会发现一个细节,在它给出的列子中,所有重复的数字都是出现了偶数次,这并不是巧合,而是必然的.
当你理解过这个问题前身的时候,也就不难想到,能不能借助前身的解决方法来解决这个问题了,但当我们将数组中的所有元素都异或时,会发现并不是我们想要的,这也就是这题的难点所在了,这是我们就要学会类推了,我们能不能想个办法来将这两个不同的数分开,然后再对其异或,不就将此问题化成我们之前的问题了吗.
#define RESULT_LEN 2
int* singleNumbers(int* nums, int numsSize, int* returnSize)
{
int *ret = (int *)malloc(RESULT_LEN * sizeof(int));
memset(ret, 0, RESULT_LEN * sizeof(int));
int s=0;
int i=0;
for(i=0;i<numsSize;i++)
{
s^=nums[i];
}
int k=1;
while(k<=s)
{
if((s&k)!=0)
{
break;
}
k<<=1;
}
for(i=0;i<numsSize;i++)
{
if((nums[i]&k)==0)
{
ret[0]^=nums[i];
}
}
ret[1]=s^ret[0];
*returnSize=RESULT_LEN;
return ret;
}
for(i=0;i<numsSize;i++)
{
s^=nums[i];
}
将数组中的所有元素异或,也就相当于两个不同的数异或,因为那些相同的数只出现了偶数次.这一步主要是找到一个区分的标准来,为下面划分做准备,因为想要将那两个不同的数分开,必须要有一个分开他们两的标准.
2.
int k=1;
while(k<=s)
{
if((s&k)!=0)
{
break;
}
k<<=1;
}
此时将得到的结果做与运算,目的是找到第一个为1的位数,借助这一位,作为区分的标准,来将数组分成2个。而且这样可以确保两个不同的数被分别分开.找到第1位为1的目的是为了将那一位对应位上为1的数分到一组,把不为1的分到一组.
3.
for(i=0;i<numsSize;i++)
{
if((nums[i]&k)==0)
{
ret[0]^=nums[i];
}
}
此时也就变成了这个问题的前身,然后我们分别对其异或就可以得到要找的数了,这里我没有将对应位上为1的数都异或,因为只要找到一个不同的数,我们就可以利用异或的性质来求出剩的那个不同的数,只要将我们找到的那个数与一开始两数异或的结果再异或一次就可以得到剩下的那个数,代码如下.
ret[1]=s^ret[0];
关于异或,有以下性质.
交换律:A ^ B = B ^ A;
结合律:A ^ (B ^ C) = (A ^ B) ^ C;
恒等律:X ^ 0 = X;
归零律:X ^ X = 0;
自反:A ^ B ^ B = A ^ 0 = A;
对于任意的 X: X ^ (-1) = ~X;
如果 A ^ B = C 成立,那么 A ^ B = C,B ^ C = A
其实异或这个运算,回想起来,我们大多数人在高中时其实就有过接触。在数学中,异或运算写成⊕.有以下性质
这个符号⊕我们在高中学数学时其实就多多少少有过接触,总有些资料题,也就是定义题中会遇见这个符号。这也就很好的证明了,其实高中学的知识都是为大学做铺垫的,而大学的一些知识也会在高中的题目里展露出来,或许这就是那些出题人强大的地方吧.
通过这题,我对异或也有了更深的认识,一开始我对它的理解停留在它的使用,相同的位则为0,不同的位则为1.后来做到交换两个数时,又对它有了一些见解,其实异或也就是2进制中的加减法。现在我对它的理解又更深了一步,理解了它的诸多性质,它可以巧妙的用来作为区分的标准.
我相信,随着学习的更加深入,也一定会对异或有更深的认识,至此,也就是我对剑指offer56题一些浅薄的认识和对异或的一些浅淡.