数组中只出现一次的三个数(简单解法)

一个int数组中除了三个数之外,其他数字都出现了两次,找出这三个只出现一次的数。

思路:只有一个数只出现的情况下,对所有数进行异或,异或的结果即为所求;只有两个数只出现一次的情况下,一个数的我们会做了,两个数的话,如何将这些元素拆分成两组,使得这两个数分布在这两组中,然后就可以一一求解。还是从所有数异或的结果入手。由于这两个数不相等,所以异或的结果一定不为0,等价于:这两个数一定存在某一个bit不相同,即:异或结果中那些为1的bit。因此任取一个为1的bit,根据这一bit是否为1,可以将原数组分为两组,其中一组该bit为1,另一组该bit为0,则这两个只出现一次的数,就分别位于这两组中,而这两组数中,除了这两个数之外,其他数仍然保持成对,因此再对这两组分别进行异或即可。

现在将思路拓展到求三个只出现一次的数。上述求两个数的方法,我们是基于一个肯定的条件——异或结果一定不为0。但这个条件在三个数的情况下,就不一定成立了。因为三个数的异或结果,可能为0。那么我们首先要解决异或结果为0的这种情况。对这三个数的bit进行分析,如下图:

《数组中只出现一次的三个数(简单解法)》

对某一位,异或和为0有两种情况:①三个bit均为0;②两个bit为1,一个bit为0。由于第一种情况下,三个数无法区分,所以我们从第二种情况入手。有两个数该bit为1,则对于该bit来说,这两个数是成对的,而剩下的那个数则是单个出现的。根据这个特点,可以首先找出落单的那个数。因为我们事先不知道这三个数,也不知道哪些bit为1,所以需要从右到左对31个bit进行检查,每次根据bit为0还是为1,将数组分成两组,如果其中一组的异或结果不为0,那么就找到了其中的一个数。接下来把这个数排除,我们就可以按照前面找两个数的方法,继续找剩下两个数了。

好了搞定三个数异或结果为0的情况了,那当异或结果不为0的时候呢?其实不需要进行复杂的证明,既然我们会解决异或结果为0的情况,那就不要浪费,想想异或结果不为0的情况怎么转化成为0的情况。很简单,一个非0的数,跟自己异或,不就得到0了吗?所以只需将数组中的数再进行一次简单的转换即可:令所有数的异或结果为sum,则将每个数都与sum再进行异或即可。得到的新数组,异或和为0。

《数组中只出现一次的三个数(简单解法)》

则求出《数组中只出现一次的三个数(简单解法)》之后,再与原《数组中只出现一次的三个数(简单解法)》异或,即可得到答案。代码如下:

class Solution {
public:
	vector<int> do_zero(vector<int>& nums){
		int sz = nums.size();
		vector<int> ans;
		for(int i = 0; i < 31; ++i){
			int base = 1 << i, sum1 = 0, sum2 = 0;;
			for(int j = 0; j < sz; ++j){
				if(nums[j] & base)
					sum1 ^= nums[j];
				else
					sum2 ^= nums[j];
			}
			if(sum1 != 0){
				ans.push_back(sum1);
				break;
			}
			if(sum2 != 0){
				ans.push_back(sum2);
				break;
			}
		}
		int cur = ans[0], sum = cur, base = 1;
		while(sum){
			if(sum & 1)
				break;
			base <<= 1, sum >>= 1;
		}
		int e1 = 0, e2 = 0;
		for(int i = 0; i < sz; ++i){
			if(nums[i] == cur)
				continue;
			if(nums[i] & base)
				e1 ^= nums[i];
			else
				e2 ^= nums[i];
		}
		ans.push_back(e1);
		ans.push_back(e2);
		return ans;
	}

    vector<int> singleNumber(vector<int>& nums) {
        int sz = nums.size(), sum = 0;
		for(int i = 0; i < sz; ++i)
			sum ^= nums[i];
		if(sum == 0)
			return do_zero(nums);
		for(int i = 0; i < sz; ++i)
			nums[i] ^= sum;
		vector<int> ans = do_zero(nums);
		for(int i = 0; i < 3; ++i)
			ans[i] ^= sum;
		return ans;
    }
};

所以解决问题的关键思路是:怎样一步一步从能够解决的问题,将思路扩展迁移到新问题上。

点赞