题目
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入: nums = [3,4,3,3]
输出: 4
示例 2:
输入: nums = [9,1,7,9,7,9,7]
输出: 1
限制:
1 <= nums.length <= 100001 <= 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;
};