# LeetCode 260、只出现一次的数字 III
# 一、题目描述
给定一个整数数组 nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
**进阶:**你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
示例 1:
输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
提示:
- 2 <= nums.length <= 3 * 10(4)
- -2(31) <= nums[i] <= 2(31) - 1
- 除两个只出现一次的整数外,
nums
中的其他数字都出现两次
# 二、题目解析
# 三、参考代码
// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 只出现一次的数字 III( LeetCode 260 ):https://leetcode-cn.com/problems/single-number-iii/
class Solution {
public int[] singleNumber(int[] nums) {
// sum 表示数组 nums 所有元素进行异或的结果
int sum = 0;
// 遍历 nums 中的每个元素
for (int num : nums){
// 将每个元素都执行异或操作
sum ^= num;
}
// 1、由于 nums 中其余元素均出现两次
// 2、异或的特征:不同为 1 ,相同为 0
// 因此,最终的 sum 就是那两个只出现一次的元素异或的结果
// 假设这两个只出现一次的元素分别为 a 和 b
// a = 011 , b = 101 , 那么 sum = 110
// 也就意味着 sum 上面的 1 必然是由 a 与 b 贡献的,因为两个相同元素的二进制必然相同,异或一下为 0
// 假设 sum 上面的第 k 位是 1
// 那么,我们可以将 nums 划分为两部分数组
// 1、第 k 位是 1,包含了 b 与 nums 中第 k 位是 1 的元素,并且这些元素出现了两次
// 2、第 k 位是 0,包含了 a 与 nums 中第 k 位是 0 的元素,并且这些元素出现了两次
// 也就是说,a 与 b 划分到了不同的部分
// 而相同的元素进行异或为 0 ,那么每一部分进行异或最终就剩下了一个只出现一次的元素
// 先去找出 sum 中任意一位为 1 的位置,我们这里去找最低位为 1 的位置
// sum & -sum 的结果是 nsum的最低位的二进制
// 比如 sum = 1011,那么它的反码是 0100,补码就是 0101
// sum = 1011
// &
// -sum = 0101
// ------------------------
// 结果就是 0001
// 即获取到了 sum 中最低位为 1 的位置
// 实际上也可以这样理解,-sum 是 sum 取反再加 1 ,那么 sum & -sum 取得的就是再加的那个 1
// 特殊情况,如果 sum = Integer.MIN_VALUE,即 sum = -2^31
// Integer.MIN_VALUE,即 -2147483648,二进制位如下:
// 1000 0000 0000 0000 0000 0000 0000 0000
// 取反后再加 1 会产生溢出的情况
int k = (sum == Integer.MIN_VALUE) ? sum : sum & -sum;
// 假设这两个只出现一次的元素分别为 a 和 b
int a = 0;
// 假设这两个只出现一次的元素分别为 a 和 b
int b = 0;
// 再次遍历 nums ,将 num 划分为两部分
for(int num:nums){
// 这部分表示第 k 位为 0 的元素,包含了 a 与 nums 中第 k 位是 0 的元素,并且这些元素出现了两次
if( ( k & num ) == 0 ){
// 这些元素进行异或,最终剩下了那个只出现一次的元素 a
a ^= num;
// 这部分表示第 k 位为 1 的元素,包含了 b 与 nums 中第 k 位是 1 的元素,并且这些元素出现了两次
}else{
// 这些元素进行异或,最终剩下了那个只出现一次的元素 b
b ^= num;
}
}
// 结合为数组的形式进行返回
int ans[] = { a , b } ;
// 返回结果
return ans;
}
}