260. 只出现一次的数字 III
根据左神的数据结构和算法课程整理
给定一个整数数组 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]
位运算的知识
- 按位与 &:两位全为1,才为1,否则为0
- 按位或 |:两位有一个为1,才为1,否则为0
- 按位取反:0变为1,1变为0
- 按位异或 ^:两位一个为1,一个为0时的结果为1,否则为0
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
- 怎么确定某个二进制数m,最后一个1在哪里
这个数m先-1,然后和m取按位与,再和m取异或即可
//比如数m是
110010
//先拿到这个数-1的结果
110001
//然后两个数取并,也就是只有同为1的时候才为1
110000
//然后和m取与运算
000010
//这个结果就是最后1位是1的结果
很神奇吧!
解题思路
和137题那个方式类似,也可以采用位运算来拿到出现一次的值,但这次是有两个元素a和b出现一次,所以得到的值是它们两个位运算的结果m,那么最核心的问题就是怎么从这个结果中得到这两个数a和b?
既然a和b不相等,它们异或运算的结果必然不等于0,所以它们异或运算必然至少有一位是等于1的,那么先要找到计算完的结果最右边哪一位等于1。
根据上面那个计算方式可以计算找出最后为1的位数,然后得到其余位都为0的一个数n。
找到了这一位异或结果等于1了,可以对这一位进行分类,分为为0的数和不为0的数,只需要看为0的这一边,其余的数都出现了2次,所以取异或为0,那么这一堆那个位为1的数都取异或,最后得到的数就是a。
然后知道了a^b的结果,又知道了a,就很好求出b了。
代码实现
public class SingleNumberIII {
public static void main(String[] args) {
int[] arr = new int[]{2,2,2,1,1,1,1,3,3,4,4,4,4,5};
int[] number = SingleNumberIII.singleNumber(arr);
System.out.println(Arrays.toString(number));
}
public static int[] singleNumber(int[] nums) {
//让所有数都进行异或^计算
int eor1 = 0;
for (int i = 0; i < nums.length; i++) {
eor1 = eor1 ^ nums[i];
}
//得到了最后这个计算结果,这个结果是数a,和数b(两个唯一数的异或结果)
//那么就找到二进制最后为1的数
int eor2 = eor1 & (eor1-1) ^ eor1;
int eor3 = 0;
for (int i = 0; i < nums.length; i++) {
if ((nums[i] & eor2) != 0){
eor3 = nums[i] ^ eor3;
}
}
int[] result = new int[]{eor3,eor1 ^ eor3};
return result;
}
}