[剑指offer]-数组中只出现一次的两个数字

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

 

知识补充:

异或的性质及运用

 异或是一种基于二进制的位运算,用符号XOR或者 ^ 表示,其运算法则是对运算符两侧数的每一个二进制位,同值取0,异值取1。它与布尔运算的区别在于,当运算符两侧均为1时,布尔运算的结果为1,异或运算的结果为0。

简单理解就是不进位加法,如1+1=0,,0+0=0,1+0=1。

性质

1、交换律

2、结合律(即(a^b)^c == a^(b^c)) 重点!!!

3、对于任何数x,都有x^x=0,x^0=x

4、自反性 A XOR B XOR B = A xor  0 = A

 

思路:

要求时间复杂度是O(n),空间复杂度是O(1)。

大家首先想到的是顺序扫描法,但是这种方法的时间复杂度是O(n^2)。接着大家又会考虑用哈希表的方法,但是空间复杂度不是O(1)。

 

我们知道异或的一个性质是:任何一个数字异或它自己都等于0。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字。比如数组{4,5,5},我们先用数组中的第一个元素4(二进制形式:0100)和数组中的第二个元素5(二进制形式:0101)进行异或操作,0100和0101异或得到0001,用这个得到的元素与数组中的三个元素5(二进制形式:0101)进行异或操作,0001和0101异或得到0100,正好是结果数字4。这是因为数组中相同的元素异或是为0的,因此就只剩下那个不成对的孤苦伶仃元素。

现在好了,我们已经知道了如何找到一个数组中找到一个只出现一次的数字,那么我们如何在一个数组中找到两个只出现一次的数字呢?如果,我们可以将原始数组分成两个子数组,使得每个子数组包含一个只出现一次的数字,而其他数字都成对出现。这样,我们就可以用上述方法找到那个孤苦伶仃的元素。

我们还是从头到尾一次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数组的异或结果。因为其他数字都出现了两次,在异或中全部抵消了。由于两个数字肯定不一样,那么异或的结果肯定不为0,也就是说这个结果数组的二进制表示至少有一个位为1。我们在结果数组中找到第一个为1的位的位置,记为第n位。现在我们以第n位是不是1为标准把元数组中的数字分成两个子数组,第一个子数组中每个数字的第n位都是1,而第二个子数组中每个数字的第n位都是0。

 

举例:{2,4,3,6,3,2,5,5}

我们依次对数组中的每个数字做异或运行之后,得到的结果用二进制表示是0010。异或得到结果中的倒数第二位是1,于是我们根据数字的倒数第二位是不是1分为两个子数组。第一个子数组{2,3,6,3,2}中所有数字的倒数第二位都是1,而第二个子数组{4,5,5}中所有数字的倒数第二位都是0。接下来只要分别两个子数组求异或,就能找到第一个子数组中只出现一次的数字是6,而第二个子数组中只出现一次的数字是4。

 

思路总结:

本文思路有两大亮点:

【1】一个整数数组中如果除了其中一个数以外,其他数都出现了两次,将这些数相异或得到的是这个单一的数。

【2】如何将两个单一的数字分到两批数中,将所有数异或,得到的结果其实就是两个单一数字的异或结果,根据其中某一位是否为1将两个数字分开,其他数字也根据这个标准分成了两批。 最终分成的两批数字中,每一批数字中,只有一个单一数字。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int len = data.size();
        if(len < 2)
            return;
        //对原始数组每个元素依次求异或(异或为位运算)
        int temp=0;
        for(int i=0;i<len;i++)
            temp ^= data[i];
        int indexOf1 = rfindFirst1(temp);
        *num1 =0;
        *num2 =0;
        for(int i=0;i<len;i++){
            if(isBit1(data[i],indexOf1))
                *num1 ^=data[i];
            else
                *num2 ^=data[i];
        }
    }
private:
    //从右找到某个数二进制形式下第一次出现1的下标
    int rfindFirst1(int num){
        int bit1 = 1;
        int index =0;
        while((num & bit1)== 0 && (index < 8*sizeof(int))){
            bit1 = bit1<<1;
            index++;
        }
        return index;
    }
    //判断某个数index位是否为1
    bool isBit1(int num,int index){
        num = num>>index;
        return (num & 1);
    }
};

 

二刷的时候,最终对每个数移位的时候不是通过对该数移动、位之后再判断其是否为1,而是直接通过位与,直接判断相应位是否为1,但是遇见了个大坑!!!

直接写 a & flag != 0;会判断错误

经验教训!!!,该多加个括号的时候不要吝啬!!!!

class Solution {
public:
	void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
		int len = data.size();
		if (len < 2)
			return;
		int temp = 0;
		for (int i = 0; i < len; i++) {
			temp ^= data[i];
		}
		int flag = 1;
		while ((temp & flag) == 0) {
			flag = flag << 1;
		}
		*num1 = 0;
		*num2 = 0;
		for (int i = 0; i < len; i++) {
			//bool aa = isfalag1(data[i], flag);
			if (isfalag1(data[i], flag)) {
				*num1 ^= data[i];
				cout << "第1组" << data[i] << endl;
			}
				
			else {
				*num2 ^= data[i];
				cout << "第2组" << data[i] << endl;
			}
				
		}
	}
    //通过与flag相位与,判断每个数相对于temp中从右到左的第一个为1的位是否为1
	bool isfalag1(int a, int flag) {
		if ((a & flag) != 0) //大坑!!!直接写 a & flag != 0;会判断错误
			return true;
		else
			return false;
	}
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值