题目描述
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
示例 1:
输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
示例 2:
输入:nums = [-1,0]
输出:[-1,0]
示例 3:
输入:nums = [0,1]
输出:[1,0]
提示:
2 <= nums.length <= 3 * 10^4
-2^31 <= nums[i] <= 2^31 - 1
除两个只出现一次的整数外,nums 中的其他数字都出现两次
解题分析
本题利用异或可以有效满足题目要求的 算法应该具有线性时间复杂度,常数空间复杂度。
首先了解一下异或 即相异为0 相同为1,异或有如下特性:
- 恒等律:a⊕0 = a
归零:b⊕b = 0
自反 :a⊕b⊕b=a
对数组中所有元素进行异或的话 由于数组数组中肯定有两个 没有重复出现的元素 其他元素均出现两次 则重复出现的数 异或的结果中 它们的二进制位一定被置为0
由于那两个没有重复的元素 所以异或的结果 肯定含有 1
即 nums 中出现两次的元素都会因为异或运算的性质 a⊕b⊕b=a 抵消掉(b⊕b = 0,a⊕0 = a),
那么最终的结果就只剩下 x1 和 x2 的异或和。
然后取异或结果中任意一 二进制位为1的位,nums中所有该位为1元素的为一组
然后取该位不为 1 的为一组
(因为相异为1 所以可以把两个目标答案分离开来 即这一位为1的是一个答案 这一位是0的是另一个答案)
这样答案就被区分为分别包含 x1 和 x2 两组
对两组单独再次全员异或 即可求出 每组中只出现过一次的值
code
public int[] singleNumber(int[] nums) {
if(nums.length == 2) return nums;
int temp = 0;
for(int num : nums) temp ^= num;
int i = 0;
for(; i < 32 ; i++){
if(( (temp >> i) & 1) == 1) break; // 取任意为1的位记录为 i
}
int[] res = new int[2];
for(int num : nums){
if(( (num >> i) & 1) == 1) res[0] ^= num; // 对第i位为1的全部异或 即取出其中一个答案
else res[1] ^= num; // 对第0位为0的全部异或 即取出另一答案
}
return res;
}
总结
本来题目一拿到就是利用HashSet剔除重复元素,结果一跑发现结果真的拉跨,就翻了一下题解,发现了位运算的解法,异或可以解决类似这样查找一堆重复出现元素中没有重复出现的元素,因为它的特性可以将重复出现的元素抵消。
岁月悠悠,衰微只及肌肤;热忱抛却,颓废必致灵魂