这是我参与11月更文挑战的第 9 天,活动详情查看:2021最后一次更文挑战
题目
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
思考
如果不考虑时间复杂度和空间复杂度的限制,可以有多种解法:
- 使用哈希表存储每个数字和该数字出现的次数。
- 使用集合存储数字。第一次遍历时加入集合,第二次遍历时从集合中删除该元素。
- 利用集合去重、求和,再与去重前的求和结果做数学运算。
以上方法都需要使用 O(n) 的空间复杂度,其中 n 是数组长度。
如何做优化,使得解法能做到线性时间复杂度和常数空间复杂度呢?
答案是使用位运算,借助异或运算去优化解法。
异或运算有以下三个性质:
- 任何数和 0 做异或运算,结果仍然是原来的数。
- 任何数和其自身做异或运算,结果是 0。
- 异或运算满足交换律和结合律。
那么,对于数组nums,对每个元素依次进行异或运算,由于出现两次的数进行异或运算得到0,因此最终结果是只出现一次的数字,也就是方法二的解法。
位运算的解法看上去是优雅了很多!
解答
方法一:哈希表
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 [num, val] of map.entries()) {
if(val === 1) {
ans = num
}
}
return ans;
};
复杂度分析
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。
- 空间复杂度:O(n),即为哈希映射需要使用的空间。
方法二:位运算
var singleNumber = function(nums) {
let result = 0;
for(let i = 0; i < nums.length; i++){
result ^= nums[i];
}
return result;
};
复杂度分析
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。只需要对数组遍历一次。
- 空间复杂度:O(1)。