题目
一个数组只有两个数字是出现一次,其他所有数字都出现了两次。编写一个函数找出这两个只出现一次的数字。
解题思路
- 什么是异或?
二进制、同0异1 - 异或有什么特点?
-
满足交换律(顺序不会影响异或的结果)
-
a ^ a =0 ; 0 ^ a=a 即自身异或为0,0与其他数异或为1
-
分步解析
- 令给定数组中ret=所有的数字异或 (数组中出现两次的数字会因为异或抵消自身异或为0,0与其他数异或为1,最终异或的结果为两个只出现一次的数字相异或的结果)
int ret = 0;
int i = 0;
for (i = 0; i < sz; i++) //sz为给定数组的个数
{
ret ^= *(str + i);
}
2.分离两个只出现一次的数字
由第一步,我们最终得到了 ret
,由上图中我们对于异或运算的分析可知,ret 第 n 位为1,对于要找出的只出现一次的这两个数字必定有如下情况:
二进制——数字一的第 n 位为0,数字二的第 n 位为1。由此,我们可以通过这个特点来分离(也就是下面采用的分组异或的方式)这个两个只出现一次的数字。
👉把只出现一次的两个数字分别分到两个不同的组
👉分组依据:计算出ret
(二进制表示的)第几位(用pos
标记)按位与1==1
int pos = 0;
for (i = 0; i < 32; i++)
{
if ((ret >> i) & 1 == 1)
{
pos = i;
break;
}
}
- 让两组分别异或得出要找的两个数
譬如:
int str[10] = { 1,2,3,4,5,1,2,3,4,6 };
分组1 num1=1^1^2^2^5=5
分组2 num2=3^3^4^4^6=6
代码示例
C语言:
int* function(int* str, int sz, int arr[2])
{
int ret = 0;
int i = 0;
for (i = 0; i < sz; i++)
{
ret ^= *(str + i);
}
int pos = 0;
for (i = 0; i < 32; i++)
{
if ((ret >> i) & 1 == 1)
{
pos = i;
break;
}
}
for (i = 0; i < sz; i++)
{
if ((*(str + i) >> pos) & 1 == 1)
{
arr[0] ^= *(str + i);
}
else
{
arr[1] ^= *(str + i);
}
}
return arr;
}
#include <stdio.h>
int main()
{
int str[10] = { 1,2,3,4,5,1,2,3,4,6 };
int sz = sizeof(str) / sizeof(str[0]);
int arr[2] = { 0 };
function(str, sz, arr);
printf("%d %d\n", arr[0],arr[1]);
return 0;
}
C++:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums)
{
int tmp=0;
for(auto e:nums)
tmp ^= e;//tmp最终为两个只出现一次的两个元素的异或
int pos=0;
for(int i = 0;i < 32;++i)
{
if((tmp>>i) & 1 == 1)//找出tmp二进制表示中为1的位置(同0异1)
{
pos = i;
break;
}
}
int ret1=0,ret2=0;
for(auto e:nums)//分组异或
{
if((e>>pos) &1==1)//二进制第pos位置为1的一组异或
ret1 ^= e;
else//二进制第pos位置为0的一组异或
ret2 ^= e;
}
return {ret1,ret2};
}
};
END