“求只出现一次的数字”系列算法问题

目前遇到的“求只出现一次的数字”系列的算法题目主要有如下三个: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 的次数状况:

  • ones 表示每一个位只出现了 1 次 1 的状况
  • twos 表示每一个位只出现了 2 次 1 的状况
  • threes 表示每一个位只出现 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;
}
复制代码
相关文章
相关标签/搜索