题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
常识:异或运算
对于同一位,只要两个值相同就为0,不同就为1,(与或运算不同,或运算只要有1就为1,没有1才是0,即1或1等于1,0或0等于0;1或0等于1;0或1等于1),在计算机中,总是使用1代表true,0代表false, 1异或0等于1;1异或1等于0;0异或1等于1;0异或0等于1(即不同时表示百花齐放是1,相同时表示没有特色是0)真^假=真;假^真=真;假^假=假;真^真=假;java中异或运算的符号是^
数字进行异或运算时首先转换为相同位数的二进制数,然后每一位对应进行异或运算,相同得到0,不同得到1,最后在转化为对应的十进制数。使用异或运算的一个典型应用是两个相同的数进行异或运算一定得到0,即对于任何数a,有a异或a等于0;同时异或运算具有传递性,a^b^c=a^c^b(从它们的本质即位运算上可以很容易的理解)
于是有一个定理:两个数异或运算等于0说明这里两个数一定相同;两个数相同则它们异或运算一定得到0;
使用这个定理有一些应用:对于一个数组,如果里面元素两两相同,只有一个数字不同,那么将所有数字作异或运算,由于相同的元素结果都为0,于是最终剩下的结果就是那么单独的元素值。---用于找出两两相同数组中唯一单独出现的数值。
本题中就可以使用异或运算的这个特性,如果一个数组中数值两两相同,只有一个数值唯一,那么对这个数组中的所有元素进行异或运算得到的结果就是这个单独的值。由于本题中有两个单独的数,因此直接对整个数组进行异或运算或得到一个非0的值,但是无法得出这两个值是什么,因此,需要先将整个数组进行拆分,将它分成两个数组,使得2个单独的元素分别出现在两个数组中,于是对每个数组使用异或运算就可以得到两个单独的值。
如何拆分数组:{2,4,3,6,3,2,5,5}
详见剑指offer:先对整个数组进行异或运算,如果为0则没有两个不同的数字,说明数组中全部元素都成对出现(题目不会出现这种情况);一半异或结果得到一个不为0的数num,根据num二进制的右侧第一个1所在位数是否为1可以将数组分成两个子数组(例如num=2其二进制位0010,其右侧倒数第二个1一定是由于两个单独数字a,b在倒数第二位一个为1,一个为0造成的),因此求出num要右移多少次count才会到这个位置,然后对数组中每个元素都右移count次,看其是否为1(通过与1:0001作&运算看结果是否为0可以判断),从而将数组分成两个部分(分别放入到方法中传入的两个数组中),这里要注意在堆num向右侧进行移位时不要超出num本身具有的位数(当num为0时可能一直移位导致溢出,但是由于已经排除num为0,因此实际上并不会溢出)
注意几个细节:①二进制的逻辑运算与、或、非、异或等运算的优先级很低,比==比较运算的优先级还要低,因此需要加上括号(num&flag)==0
②巧妙使用flag=1的移位运算来检验num各位的值是否为1或者0
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
//要求时间复杂度为O(n),空间复杂度为O(1),本题使用位运算中的异或运算的特性,是异或运算的典型应用
public class Solution {
public voidFindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
//特殊输入检验;空输入或者输入不满足条件时直接结束方法,不用返回
if(array==null||array.length<2) return;
//对整个数组进行异或运算
int num=0;
for(inti=0;i num^=array[i]; } //如果num==0表示数组中全部元素都是成对出现,不符合题意 if(num==0)return; /*巧妙处理:找出num二进制中右侧第一个为1的位置(转化为找到一个最小的flag,使得flag与num作&运算不为0)即不断调整比较的位置flag=0001;flag=0010;flag=0100*/ int flag=1; while((num&flag)==0){ flag=(flag<<1); } //此时flag中1所在的位置m就是num中右侧第一个1所在的位置,通过使用flag就不用记录num右移的count,其实也一样 //将数组分成两个子数组,分别进行异或运算(这里子数组并没有创建出来,因为要求不能占用空间复杂度) num1[0]=0; num2[0]=0; for(inti=0;i if((array[i]&flag)==0){ //&运算优先级很低,要加上括号 //说明这个元素在m位置为0,将其放入第一个子数组 num1[0]^=array[i]; }else{ //说明这个元素在m位置为1,将其放入到第二个子数组 num2[0]^=array[i]; } } }