一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。
要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums <= 10000
思路:我自己本身用map来做,空间复杂度没做到O(n)。不过学习到了用异或解决的操作,解析传送门
异或运算:
0 ^ 0 = 0,0 ^ 1 = 1 可以看作0异或任何数都等于这个数
1 ^ 0 = 1,1 ^ 1 = 0 可以看作1异或任何数都等于这个数取反
因为相同的两个数异或会变为0,而0和任何数异或又会变成那个数。所以如果在一串数字中只有一个数字出现1次,而其余数字每个都出现2次的情况下,让一个变量(初始为0)和每个数字都异或一遍就好了:
4 ^ 3 ^ 3 ^ 4 ^ 6 => 6
#include <iostream>
using namespace std;
int main()
{
int arr[9] = {1, 2, 2, 3, 3, 5, 5, 6, 1};
int num = 0;
for (int i = 0; i < 9; i++)
num ^= arr[i];
cout << num;
return 0;
}
输出:6
但是现在题目要求一串数字中有两个数字出现1次,其余出现两次,该怎么解决呢?
先进行上面一样的操作,把所有的数字进行一次异或,能得到两个数字的异或:
4 ^ 1 ^ 4 ^ 6 => 1 ^ 6
6 对应的二进制: 110
1 对应的二进制: 001
1 ^ 6 二进制: 111
因为这两个数字不等(就像上面1和6在第一位不等一样),因此他们的二进制必定至少1位不同,即异或结果中为1的那位(一个数字的该位为1,另个数字的该位为0)。从右向左找出这个不同的位置(异或值为1的位置),mask在该位置设置成1,其余位置是0。mask存在的意义在于我们能通过该位置来分辨出两个只出现了一次的数字。
再让每个数字与mask相与。通过与的结果为0和为1,再用异或即可区分出两个数字(因为与运算符是0与任何数都是0,所以唯一的那个1能把不同的数区分开)
同样,重复的数字会异或为0被抵消掉,最后留下唯一的数字。
代码实现:
vector<int> singleNumbers(vector<int>& nums) {
int ans = 0;
for (int i : nums)
ans ^= i;
int mask = 1;
while ((ans & mask) == 0)
mask <<= 1;
int a = 0, b = 0;
for (int i : nums)
{
if (i & mask)
a ^= i;
else
b ^= i;
}
return {a, b};
}