Leetcode-260 只出现一次的数字 III

242 阅读3分钟

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了。

image-20211023124323757

代码实现

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;
    }
}