位操作相关题目-数组中某数字只出现了一次及多种进阶版本

18 篇文章 0 订阅

原题:

有一个数组,其中有一个数字只出现了一次,其余的数字均出现了两次,请找到该数组.

class Solution {
public:
	int FindNumber(vector<int> vec) {
		int len = vec.size();
		if (len == 0) return -1;
		int res = 0;
		for (int i = 0; i < len; i++) {
			res = res^vec[i];
		}
		return res;
	}
};

这种题目可以通过排序,或者建立哈希表查找。但排序复杂度为O(n log n),建立哈希表则是需要额外的O(n)空间复杂度。

通过位操作则可以巧妙解决该问题。


注意到,异或操作的特点: 1)两个相同的数异或结果为0, 

                                       2)与0异或得到该数本身,

                                       3) 异或操作满足交换性。

基于这两个性质,上题可以这样解决:异或遍历整个数组,则所有出现两次的数字异或之后结果为0,0与只出现了一次的数字异或结果是该数本身。

所以最终的结果就是数组中所有数字异或的结果。


进阶版:

进阶一:

一个数组中,有两个数字只出现了一次,其余的数字都出现了两次,求找到出现一次的两个数字分别是哪个


分析:如果是一个数字只出现一次,直接有原型解决。现在出现了两个只出现一次的,该如何解决呢?

          注意到,两个不同的数字异或结果不为0,即总有一位是1(也可能有多个位置为1)。正是这一位的结果构成了两个不同数之间的区别。

          根据该位置为1和为0,我们可以将原数组分为两类,一个是该位置为1的,必然包括一个只出现一次的数字和多组出现两次的数字(两个相同的数字每一位的bit表示都一样,所以必然会被分到同一类中);另一类是该位置为0的。分别对每一类做异或操作,得到结果即为所求。

#include<iostream>
#include<vector>

using namespace std;

class Solution {
public:
	void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
		int len = data.size();
		if (len < 2) return;
		int res = 0;
		for (int i = 0; i < len; i++) {
			res ^= data[i];
		}
		int idx = FindBit1(res);
		*num1 = 0; *num2 = 0;
		for (int i = 0; i< len; i++) {
			if (isBit1(data[i], idx)) {
				*num1 = *num1 ^ data[i];
			}
			else {
				*num2 = *num2 ^ data[i];
			}
		}

	}
	bool isBit1(int num, int k) {   //从右边数第k位是否为bit 1
		int count = 1;
		unsigned int flag = 1;
		while (count < k) {
			flag = (flag << 1);
			count++;
		}
		if (flag & num) return true;
		return false;
	}
	int FindBit1(int num) {
		unsigned int flag = 1;
		int count = 1;
		while (!(num & flag)) {
			flag = (flag << 1);
			count++;
		}
		return count;
	}
};


int main()
{
	Solution s;
	vector<int> vec{ 1,2,2,1,5,3,5,9 };
	int num1 = 0; int num2 = 0;
	s.FindNumsAppearOnce(vec, &num1, &num2);
	cout << num1 << " " << num2 << endl;
	return 0;
}


进阶二:

一个数组中,只有一个数字出现了一次,其余的数字均出现了三次,求找到只出现一次的数字。

分析:

因为三个相同的数字异或的结果还是其本身,所以上面题目的思路无法适用此题。但可以延续位操作的思路。

对每一个出现三次的数字来说,其二进制表示完全相同,如果把二进制表示的每一位都分别加起来,则每一位的和都可以被3整除。

所以,将所有的数字二进制表示的每一位都分别加起来之后,每个无法被3整除的位都是因为只出现了一次的那个数字造成的。

#include<iostream>
#include<vector>

using namespace std;

class Solution {
public:
	int FindNumber(vector<int> vec) {
		int len = vec.size();
		if (len == 0) return -1;
		return Find(BitSum(vec), BitSum(vec).size());
	}
	vector<int> BitSum(vector<int> vec) {
		int len_res = sizeof(int) * 8;
		vector<int> res(len_res);
		for (int i = 0; i < vec.size(); i++) {
			unsigned int flag = 1;
			for (int j = len_res-1; j >= 0; j--) {
				if (vec[i] & flag) {
					res[j]++;
				}
				flag = flag << 1;
			}
		}
		return res;
	}
	int Find(vector<int> res, int length) {
		int num = 0;
		int flag = 1;
		for (int i = length - 1; i >= 0; i--) {
			if (res[i] % 3) {
				num += flag;
			}
			flag = flag << 1;
		}
		return num;
	}
};


int main()
{
	Solution s;
	vector<int> vec{ 1,2,2,1,1,3,2};
	int  res = s.FindNumber(vec);
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值