这是我参与11月更文挑战的第 10 天,活动详情查看:2021最后一次更文挑战
题目
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次。请你找出并返回那个只出现了一次的元素。
示例 1:
输入:nums = [2,2,3,2]
输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99]
输出:99
提示:
1 <= nums.length <= 3 * 10^4-2^31 <= nums[i] <= 2^31 - 1nums中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
进阶:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
思考
这道题是136_只出现一次的数字的升级版。
首先,我们可以想到的是使用哈希表,记录数组中每一个元素出现的次数。然后使用for循环遍历哈希表,找出哈希表中只出现了一次的元素。该算法的时间复杂度和空间复杂度都为O(n)。
其次,我们可以借助位运算去优化算法的空间复杂度。考虑到数组元素出现了三次,那么,对于数组元素,其二进制形式的每一位,出现1的次数会是3的倍数。基于此,求余之后,得到的便是数组中只出现了一次的元素。例子:
例子:nums = [3,3,5,3]
11
11
101
11
数组元素的每一位,出现1的次数分别为1、3、4, 对3求余:
134 % 3 = 101 -> 5
该方法很巧妙!
解答
方法一:哈希表
var singleNumber = function(nums) {
let map = new Map();
let ans = 0;
for (const num of nums) {
map.set(num, (map.get(num) || 0) + 1);
}
for (const [key, value] of map.entries()) {
if (value === 1) {
ans = key;
}
}
return ans;
};
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。
- 空间复杂度:O(n)。
方法二:依次确定每一个二进制位
var singleNumber = function (nums) {
let ans = 0;
for (let i = 0; i < 32; i++) { // 数组元素在 int(即 32 位整数)范围内
let temp = 0;
for (const num of nums) {
// 通过移位取出每个元素的最后一位,计算nums中最后一位为1的个数
temp += (num >> i) & 1;
}
if (temp % 3 != 0) {
// 左移至原位,按位或并赋值
ans |= (1 << i);
}
}
return ans;
};
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。
- 空间复杂度:O(1)。
注释:(num >> i) & 1
当 num = 3,i = 0 时,
num >> i === 11 >> 0 === 11
11 & 1 === 1 // 得到 num 的第 i 个二进制位