面试题56 - I. 数组中数字出现的次数
题目
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路
- 题目中需要找出只出现了一次的的两个数字,并且其他数字只出现了两次。
- 因为其他数字只出现了两次,又因为我们知道两个相等的数字进行异或运算可得 0 ,及
a & a = 0,不相同的一定不会为 0 。 - 如果仅有一个数字只出现一次的话,那么就可以通过全部数字进行异或运算即可得到出现了一次的数字。
- 但是此题中出现了一次的数字有两个,那么该怎么做呢?
- 我们可以想到将这堆数字分成分别含有两个出现了一次数字的两堆,且每一堆中除了一个出现了一次的数字,其他都是成对的。
- 我们可以想到两个不同的数字做异或的结果的二进制中有数字 1 的位置,一定是这两个数字的二进制中其中一个数字在这个位置的数字为 1 ,而另一个为 0 ,例如:
1010 0101 & 0001 1010 = 1011 0100。 - 那么我们根据任意这样的位置来将这一堆数字进行分成两类。而且二进制不同的话数字一定也不一样,也可以很好的符合之前的需求。
代码
- 先要得到那两个数字的异或值,及全部数字进行异或。
let sum = nums.reduce((a, b) => a ^= b);
- 得到可以通过二进制来表示异或值
sum在二进制中任意一个有 1 的位置。
let key = 1;
while (!(sum & key)) {
key <<= 1;
}
- 对一堆数字分组,并且对任意一堆进行异或运算。
let sum_1 = 0;
nums.map(num => {
(num & key) && (sum_1 ^= num);
});
- 将得到的结果
sum_1和sum进行异或算出另一个数字,并返回。
return [sum_1, sum ^ sum_1];
- 整合
/**
* @param {number[]} nums
* @return {number[]}
*/
var singleNumbers = function (nums) {
let sum = nums.reduce((a, b) => a ^= b);
let key = 1;
let sum_1 = 0;
while (!(sum & key)) {
key <<= 1;
}
nums.map(num => {
(num & key) && (sum_1 ^= num);
});
return [sum_1, sum ^ sum_1];
};
console.log(singleNumbers([1,2,10,4,1,4,3,3]));