剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
解题思路
我们先从这个问题的简化版本开始:一个整型数组 nums 里除一个数字之外,其他数字都出现了两次。
要解决这个问题,我们需要熟悉位运算符异或的功能:两个bit A和B做异或,若A与B相同,则返回0;否则返回1
即
0 ^ 0 = 0;
0 ^ 1 = 1;
1 ^ 0 = 1;
1 ^ 1 = 0;
知道这一点后,我们可以容易地解决上述问题:只需将数组nums内的所有数字都做异或运算,最后得到的结果就是那个只出现一次的数字;
具体来说,比如有数组 1, 2, 1, 3, 4, 4, 3
我们做异或
1 ^ 2 ^ 1 ^ 3 ^ 4 ^ 4 ^ 3
= (1 ^ 1) ^ (3 ^ 3) ^ (4 ^ 4) ^ 2
= (0 ^ 0 ^ 0) ^ 2
= 2
回到我们原来的问题,现在给定的数组是 1, 2, 1, 3, 4, 4, 3 , 5; 多了一个只出现一次的数字5,现在有两个只出现一次的数字2 和 5,其余数字都出现两次;
如何把问题简化成我们上面说的只有一个数字只出现一次,使得我们能够方便地用异或解决?——可不可以把这两个只出现一次的数字分在两个组里?换句话说,我们能不能规定一个分组方法,把原数组分割成(1,2,1) 和 (3,4,4,3,5), 这样我们就能对这两个组分别运用上述的异或方法来找出只出现一次的数字;
如果我们按照元素的奇偶性分组,那么可以分成两组如下: (1,1,3,3,5) 和(2,4,4);这样的两组分组是我们所期望的,但是如果两个出现一次的数字的奇偶性相同时,这个分组方法就失效了。
所以我们必须使用一种能够确保这两个出现一次的数字分别被分在两个组内的分组方法;这里给出一种分组方法:
对这两个只出现一次的数字异或:
2 ^ 5
= 0010 ^ 1010
= 1001
从这个异或结果中得到的信息是:这两个只出现一次的数字的最低位和最高位是不相等的(因为异或结果中的对应位等于1);
所以我们可以根据这个结果来进行分组:把最低位 = 0的数组元素分为一组, 最低位=1的数组元素分为另一组,这样就能够把两个只出现一次的数组元素分到两个不相交的组,得到符合要求的分组;
对于那些出现两次的数组元素,我们不需要为他们额外考虑,因为按照我们的分组方法,两个相等的数组元素必定会被分配到同一组中去;
代码实现:
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int A_exclusiveor_B = 0; // A异或B的结果
for (int num : nums)
A_exclusiveor_B ^= num;
int Inequal_Bit = 1; // A和B不相等的那一位;这是我们可以从A异或B的结果中得到的信息
while ((Inequal_Bit & A_exclusiveor_B) != Inequal_Bit)
Inequal_Bit = Inequal_Bit << 1;
int A = 0, B = 0;
for (int num : nums) { // 分组异或
if (num & Inequal_Bit)
A ^= num;
else
B ^= num;
}
return vector<int> {A,B};
}
};