[面试题56 - I. 数组中数字出现的次数]
难度:中等
一个整型数组 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof
方法一:暴力解法
在不考虑时间和空间的情况下,可以使用HashMap或者数组来保存每个数字出现的次数,最后筛选出只出现一次的两个数字。由于该方法无法满足题目**时间复杂度是O(n),空间复杂度是O(1)**的要求,在此不详细表述。
方法二:分组异或
预备知识
异或运算:将两个数字a 和 b 的每一位二进制进行运算,具体规则为:如果同一位的二进制数字相同则该位结果为 0,否则为 1。
异或运算的性质:①交换律 a ^ b ^ c = a ^ c ^ b
②结合律 a ^ b ^ c = (a ^ b)^ c
③对于任何数x,都有 x ^ x = 0 , x ^ 0 = x
。
思路
先来考虑,数组中除一个数字外,其它数字都出现两次的情况。
可以对全部数字进行异或操作,由异或操作的性质和数组中数字的特性可以得到:经过异或操作 后,相同的数字两两异或操作后结果为0,最终结果即为仅出现一次的数字。
例如: nums = [a, b, c, d, a, b, c]
, 在该数组中 d 为出现一次的数字,对所有元素进行异或操作后有 (a ^ a) ^ (b ^ b) ^ (c ^ c) = 0
(交换律和结合律), 0 ^ d = d
。
那么数组中有两个数字出现一次的情况该怎么处理呢?
我们可以将数组中的数字分为两组,其中每组除了包含成对的相同数字外,还各包含一个出现一次的数字,然后我们用上面处理除一个数字外,其它数字都出现两次的办法来分别处理这两部分,就得可到最终结果。
例如: nums = [a, b, c, a, b, d]
, 则可将该组分为nums1 = [a, a, c]
和 nums2 = [b, b, d]
两部分,再对两部分分别进行处理。
现在需要考虑的就是如何将数组中的只出现一次的两个数字划分到不同的组中去,且同一组中的其他数字两两成对。
分组方法:
1.重复的数字进行分组
对于重复的数字的分组十分简单,例如:可以采取奇偶分组(相同数字的奇偶性相同,一定会划分到同一组。)
2.只出现一次的两个数字分组
可以采用二进制位来分组,对于不同的两个数字,它们的二进制位中一定有一位不相同,我们只要找到这个位置,让两个数分别在该位置与其他位置为0该位置为1的数字进行 & 运算即可将这两个数分到不同的组中(其他位置为0该位置为1的数字可以保证仅判断该位置是0还是1,而其他位置与0进行&操作结果都为 0,进而达到划分的目的)。
例如: nums = [4, 1, 4, 7]
, 4 ^ 1 ^ 4 ^ 7 = 1 ^ 7 = 0110(二进制)
,对于结果0110
,我们从右往左数,第一个不为0的位置即可作为区分数字 1 和 7。
具体代码如下:
class Solution {
public int[] singleNumbers(int[] nums) {
int xor = 0; //存储所有数字的异或结果
for(int x : nums) {
xor ^= x;
}
int dis = 1; //标记数组中只出现一次的两个数的第一个不同位置
while((xor & dis) == 0) {
dis <<= 1;
}
int a = 0, b = 0;
for(int x : nums) {
if((dis & x) == 0) {
a ^= x;
}else{
b ^= x;
}
}
return new int[] {a, b};
}
}
有的人对部分代码存在一些疑惑,下面单独拿出该部分进行解释:
//xor为数组中所有数字异或的结果,同时也是数组中只出现一次的两个数字异或的结果(4 ^ 1 ^ 4 ^ 7 = 1 ^ 7 = 0110)
int dis = 1;
//从右往左数,找到第一个不为0的位置(异或结果0,表示两数在该位置的二进制数相同),如果该位置为0则进行左移操作,即比较下一个位置,1的二进制表示为0001, 7的二进制表示为 0111, 1和7在从左往右数第二个位置处的二进制不同, 即dis = 0010, 0001 & 0010 = 0 , 0111 & 0010 = 0010 ≠ 0
while((xor & dis) == 0) {
dis <<= 1;
}