一个整型数组 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]

分组异或

写了里面那一题,这题就简单多了

有两个单身狗,那么只要先找第一个,然后再找第二个就行了

但是,如何分组呢?

  1. 4 ^ 1 ^ 4 ^ 6 => 1 ^ 6
  2. 6 对应的二进制: 110
  3. 1 对应的二进制: 001
  4. 1 ^ 6 二进制: 111

此时我们无法通过 111(二进制),去获得 110 和 001。
那么当我们可以把数组分为两组进行异或,那么就可以知道是哪两个数字不同了。
我们可以想一下如何分组:

重复的数字进行分组,很简单,只需要有一个统一的规则,就可以把相同的数字分到同一组了。例如:奇偶分组。因为重复的数字,数值都是一样的,所以一定会分到同一组!
此时的难点在于,对两个不同数字的分组。
此时我们要找到一个操作,让两个数字进行这个操作后,分为两组。
我们最容易想到的就是 & 1 操作, 当我们对奇偶分组时,容易地想到 & 1,即用于判断最后一位二进制是否为 1。来辨别奇偶。
你看,通过 & 运算来判断一位数字不同即可分为两组,那么我们随便两个不同的数字至少也有一位不同吧!

我们只需要找出那位不同的数字 mask,即可完成分组( & mask )操作。

由于两个数异或的结果就是两个数数位不同结果的直观表现,所以我们可以通过异或后的结果去找 mask!
所有的可行 mask 个数,都与异或后 1 的位数有关。

  1. num1: 101110 110 1111
  2. num2: 111110 001 1001
  3. num1^num2: 010000 111 0110
  4. 可行的mask: 010000 001 0010
  5. 010 0100
  6. 100

为了操作方便,我们只去找最低位的 mask:

  1. /**
  2. * @param {number[]} nums
  3. * @return {number[]}
  4. */
  5. var singleNumbers = function (nums) {
  6. let sum = 0;
  7. for (let num of nums) {
  8. sum ^= num;
  9. }
  10. //获得k中最低位的1
  11. let mask = 1;
  12. while ((sum & mask) === 0) {
  13. mask <<= 1;
  14. }
  15. //将不同的数拆分开,想象成在两个集合中继续异或,最终剩下的数就是不同的数
  16. let a = 0, b = 0;
  17. for (let i = 0; i < nums.length; i++) {
  18. if ((nums[i] & mask) === 0) {
  19. a ^= nums[i];
  20. } else {
  21. b ^= nums[i];
  22. }
  23. }
  24. return [a, b]
  25. }