题目描述:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。例如{2,4,3,6,3,2,5,5},因为只有4,6这两个数次出现一次,所以输出4和6。
解题思路:
- 数组里有两个数字只出现一次,其他数字都出现两次。
- 异或性质:任何一个数字异或它自己都等于0.
- 将数组拆分成两个子数组,使得每个子数组包含一个只出现一次的数字。
- 首先将所有数字执行异或操作,得到result。(result是两个只出现一次的数字的异或结果)
- 在result中找到自右边起的第一个为1的位的位置,标记为n位。
- 将原数组分为两个组,第一组中每个数字的第n位都是1;第二组中每个数字的第n位都是0.
- 由于result是这两个只出现一次的数字的异或结果,所以它俩肯定被分配到不同的子数组。而其他出现两次的数字肯定一起被分配到用一个子数组。
- 再分别对两个子数组求异或,便能分别找出出现一次的数字。
测试用例:
int main(){
//输入数组
int arr[8] = {2, 4, 3, 6, 3, 2, 5, 5};
//两个结果
int num1 = 0;
int num2 = 0;
//查找
FindNumAppearOnce(arr, 8, &num1, &num2);
//输出结果
std::cout << "num1: " << num1 << std::endl << "num2: " << num2; //Output: 6, 4
return 0;
}
函数实现:
//判断在number的二进制表示中从右边数起的第一位indexBit是不是1
bool IsBit1(int number, unsigned int indexBit){
number = number >> indexBit; //右移indexBit位
return (number & 1); //如果是1,返回true
}
//在整数number的二进制表示中找到最右边是1的位
unsigned int FindFirstBitIs1(int num){
int indexBit = 0;
while( ( (num & 1) == 0) && (indexBit < 8 * sizeof(int))){ //等于0 及 小于最高位数 时循环
num = num >> 1;
++indexBit;
}
return indexBit;
}
//主函数
void FindNumAppearOnce(int *data, int length, int *num1, int *num2){
if(data == NULL || length <= 2)
return;
//对所有元素执行一次异或操作,获得两个不同数字的异或结果
int resultExclusiveOR = 0;
for(auto i = 0; i < length; ++i)
resultExclusiveOR ^= data[i];
//在resultExclusiveOR中找到自右边起的第一个为1的位
unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);
for(auto j = 0; j < length; ++j){
if(IsBit1(data[j], indexOf1)) //如果元素自右边起的第indexOf1位是1,(抽象的)放在第一个数组即与num1再次执行异或操作
*num1 ^= data[j];
else
*num2 ^= data[j]; //如果元素自右边起的第indexOf1位是0,(抽象的)放在第二个数组即与num2再次执行异或操作
}
//最终num1和num2分别保存了只出现一次的那两个数字
}