题目来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof
题目要求
一个整型数组 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.length <= 10000
题解
由于题目对时间复杂度和空间复杂度有要求,那么使用两重循环嵌套暴力寻找(时间复杂度为O(n^2))是不可行的。因此,考虑使用异或操作来实现。
由于相等的两个数按位异或之后的值为0,那么将数组中所有的数字异或操作起来,所得的数就是我们要寻找的两个落单数字的按位异或结果。
例如:
4 ^ 1 ^ 4 ^ 6 => 1 ^ 6
6 对应的二进制: 110
1 对应的二进制: 001
1 ^ 6 二进制: 111
然而,我们只知道异或的结果,无法反推出这两个数。这时,就需要转换一下思路。如果能将这两个数区分开来,即分到两组里,再分别与两组里其它数异或,就可以得出这两个数分别是什么了。例如:
4 1 4 6 5 5分为4 1 4 和 6 5 5 两组。分别异或,4 ^ 1 ^ 4=>1,6 ^ 5 ^5=>6。这时,我们就找到了落单的1和6两个数字。
PS:由于分组规则是一定的,那么重复数字一定会分到同一组,例如两个4不可能处于不同组。
难点在于如何制定分组规则,区分开两个落单数字。
联想一下,当我们对奇偶数进行分组时,用最后一位二进制是否为1来作为规则。那么,我们可以得出:通过 & 运算来判断一位数字不同即可分为两组。
那么如何找出哪一位不同呢?这就可以用上我们刚才求得的异或结果了。
6 对应的二进制: 110
1 对应的二进制: 001
1 ^ 6 二进制: 111
可以看出,对于6和1这两个数而言,任意一位都可以作为区分的依据,因为它们的每一位都不同。即:001、010、100都可以作为区分规则。例如:
6 & 2(即010)=> 010
1 & 2(即010)=> 000
010这个值称为mask。
那么,我们使用 nums[i] & mask == 0 这个判据,就可以将 6 和 1 分入两组了。C语言中,0为假,非0为真,不是非0即1的概念。因此注意一定是 == 0 ,而不是 != 1。
最后,再将两组的异或结果存入函数返回的数组中即可。
代码如下:
#include <stdio.h>
#include <stdlib.h>
/**
* Note: The returned array must be malloced, assume caller calls free().
*/