目前遇到的“求只出现一次的数字”系列的算法题目主要有如下三个:javascript
这类算法题目的解决方案主要有如下几种:java
创建一个 Map(key 为数组中的元素,value 为该元素出现的次数),遍历数组中的全部元素,最后再遍历 Map 找到只出现一次的元素。算法
function singleNumber(nums = []) {
let map = {};
nums.forEach(num => map[num] ? map[num]++ : (map[num] = 1));
return Object.keys(map).filter(num => map[num] === 1).map(num => parseInt(num));
}
复制代码
该方法优势是比较简单,能够解决上面三个题目,可是空间复杂度比较高,若是数组元素比较多时,map 占用空间太大。数组
由于题目中的数字是 Int 类型,能够创建一个大小为 32 的数组,来统计每一位上 1 出现的个数,若是某一位上为 1 的话,那么若是该整数出现了三次或者两次(看题目中其他元素出现的次数),对 3 或者 2(具体看题目中) 取余为 0,这样把每一个数的对应位都加起来对 3 或者 2 取余,最终剩下来的那个数就是单独的数字:大数据
function singleNumber(nums = []) {
let res = 0;
for (let i = 0; i < 32; i++) {
let sum = 0;
for (let j = 0; j < nums.length; j++) {
sum += (nums[j] >> i) & 1;
}
res |= (sum % 2) << i;
}
return res;
}
复制代码
这个对于大数据量的状况下,相对于方案一,空间复杂度要小不少,可是这个方案只能解决题目一和题目三的问题,对于题目二中,有两个元素只出现一次却无能为力。ui
想到位运算中的异或操做可使两个相同的数字进行异或操做结果必为 0,那么咱们能够遍历整个数组,对全部元素进行异或操做,全部出现两次的元素异或结果都变成 0 了,那么剩下的确定就是那个只出现一次的数字了,是否是很神奇:spa
function singleNumber(nums = []) {
let res = 0;
nums.forEach(num => res = res ^ num);
return res;
}
复制代码
这个解法代码量少,时间复杂度和空间复杂度都最低。code
若是只有一个数字出现一次,咱们能够经过异或的方式直接获得该数字,若是两个数字只出现一次的状况下,获得的异或的结果必然是这两个数字的异或的结果,那么咱们如何拆分呢?咱们能够想到,若是这个结果某个位等于 1(随便找一个为 1 的位便可),那么必然是这两个数字里对应的二进制位一个是 0, 一个是 1,那么咱们就能够根据该位把列表里全部的数字分为两类,一类是该位是 0 的列表,一类是该位是 1 的列表,那么《题目二》就被拆解成《题目一》的两个子问题,一样能够获取到这两个只出现一次的数字:three
function singleNumber(nums = []) {
let res = 0;
nums.forEach(num => res = res ^ num);
let i = 0;
while(!((res >> i) & 1)){
i++;
}
let res1 = 0, res2 = 0;
nums.forEach(num => {
if((num >> i) & 1){
res1 = res1 ^ num;
}else{
res2 = res2 ^ num;
}
});
return [res1, res2];
}
复制代码
对于《题目三》,若是咱们直接使用方案一和方案的方式去解决问题,好像并无论用,由于其他元素出现的次数是三次,异或并不能使其他元素结果变为 0,若是异或操做的三进制,那么或许能够,惋惜异或目前只能操做二进制,那么有没有一种方案能够模拟三进制操做呢,答案是确定的。ip
咱们能够用 3 个整数来表示各位出现 1 的次数状况:
function singleNumber(nums) {
let one = 0, two = 0, three = 0;
for (let i = 0; i < nums.length; i++) {
//若是最近连续两次为 1,或者原先 two 就为 1,那么 two 就为 1,two 的计算必须放在 one 前面,这里的 & 是与上一次的 one
two |= one & nums[i];
//每次经过异或更新只出现的一次的结果,和《题目一》《题目二》的相似
one ^= nums[i];
//若是一个位既出现了一次,也出现了两次,表示这个数字出现了3次
three = one & two;
//清零 one,two 中出现三次的位
one &= ~three;
two &= ~three;
}
return one;
}
复制代码