剑指 Offer 56 - II. 数组中数字出现的次数 II

108 阅读1分钟

题目

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入: nums = [3,4,3,3]
输出: 4

示例 2:

输入: nums = [9,1,7,9,7,9,7]
输出: 1

 

限制:

  • 1 <= nums.length <= 10000
  • 1 <= nums[i] < 2^31

题解

如果一个数字出现3次,它的二进制表示的每一位(0或1)也出现了三次,如果把所有的出现3次的数字的二进制表示的每一位都分别相加起来,那么每一位的和都能被3整除。

把数组中的所有数字的二进制表示的每一位都相加起来,如果某一位的和能被3整除, 那么那个只出现一次的数字二进制表示中的对应的那一位是0, 否则是 1

这里以数组 nums = [3,4,3,3]为例, 它的计算流程如下:

nums = [3,4,3,3] 中的数字二进制按位相加表示如下

3:  011
4:   100
3:   011
3:   011
-----------
n    133
     
`nums = [3,4,3,3]`的数字每一位相加后的二进制表示为`n = 133`

对二进制的`n = 133`的每一位取3的余数
最低位`3 % 3 = 0`, 
次低位`3 % 3 = 0`, 
最高位`1 % 3 = 1`, 
最终得出数字的二进制表示为`100`,

最后将二进制`100`转换为十进制数字,
1 * 2^2 + 0 * 2^1 + 0 ^ 2^0 =  4,

即只出现的一次数字为4

具体的实现细节: 需要一个长度为32的辅助数组用于存储二进制表示的每一位的和,

由于数组的长度是固定的,因此空间复杂度为O(1), 时间复杂度为O(n), 这种解法比排序时间O(NlogN)、哈希表的O(n)空间效率都要高

代码

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
    // 检查输入是否合法
    if (!nums || nums.length <= 0) {
        throw new Error('Invalid Input.');
    }

    // 长度为32的辅助数组
    const bitSum = new Array(32).fill(0);

    // 遍历数组
    for (let i = 0; i < nums.length; i++) {
        // 位掩码
        let bitMask = 1;
        // 从数字二进制表示的从最低位向最高位做按位操作
        for (let j = 31; j >= 0; j--) {
            let bit = nums[i] & bitMask;
            if (bit != 0) {
                bitSum[j] += 1; 
            }
            // 位掩码向左移动一位
            bitMask = bitMask << 1;
        }
    }

    // 返回结果
    let res  = 0;

    // 对32为的二进制的每一位整除3
    for (let i = 0; i < 32; i++) {
        res = res << 1;
        res += bitSum[i] % 3;
    }

    return res;
};  

原题链接

剑指 Offer 56 - II. 数组中数字出现的次数 II