Leetcode【异或 位运算】| 面试题56 - I. 数组中数字出现的次数(java详细注释版)& 260. 只出现一次的数字 III

这篇博客介绍了如何使用位运算解决LeetCode上的两道题目,分别是找到数组中出现一次的两个数字。通过位运算的性质,如异或和按位与的规则,分析了解题思路并提供了Java实现。文章强调了解题过程中的分part策略,利用位运算将数组分成两部分,从而找出唯一出现一次的数字。
摘要由CSDN通过智能技术生成

Leetcode | 面试题56 - I. 数组中数字出现的次数 260. 只出现一次的数字 III

题目

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
 输入:nums = [4,1,4,6]
 输出:[1,6] 或 [6,1]
示例 2:
 输入:nums = [1,2,10,4,1,4,3,3]
 输出:[2,10] 或 [10,2]
限制:2 <= nums <= 10000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof

解题

基础(位运算介绍)

对几种位运算的详解请看Java 位运算
这道题主要应用的是按位异或^和按位与&的性质。
按位异或的性质:相同的数异或结果为0,不同的数异或结果为1。

  1. 归零律:a ^ a = 0;
  2. 交换律:a ^ b = b ^ a;
  3. 恒等律:a ^ 0 = a;
  4. 结合律:(a ^ b) ^ c = a ^ (b ^ c);
  5. 自反律:a ^ b ^ b = a ^ 0 = a;

按位与:a & 0 = 0; a & 1 = a; (0&0=0;0&1=0;1&0=0;1&1=1)

解题思路

假设所有数中唯一没有重复的两个数为a和b,求解nums[]中a和b的过程分为两步:

  1. nums中所有数全部异或:因为所有数中只有两个数是不重复的,其他都有重复配对。根据相同数异或为0的性质,很容易想到我们把所有的数全部异或求得结果就是所求不重复的两个数a和b的异或值a^b。(根据a ^ 0 = a 和 a ^ a = 0)
    n1 ^ n1 ^ a ^ n2 ^ n2 ^ … ^ b ^ nn ^ nn = a ^ b 因为ni ^ nI = 0,相当于都相互抵消掉了
  2. 已知a ^ b的结果怎么分解出a和b?
    2-1. 分part: 将整个数组nums[]分为两部分A[]和B[],要求①将a和b分别分布在两个部分;② 相同的数一定在同一个部分内。
      这样 A[] = {a,n1,n1,n2,n2,…nk,nk},B[] = {b,nk+1,nk+1,nk+2,nk+2,…nn,nn}
       求解: a = A部分内所有数字异或结果,b = B部分内所有数字异或结果。
    2-2. 怎么实现这样的分part?
      因为a,b不同,所以异或结果restmp中必然会出现至少一个1,将a^b结果中的最低位的“1”求出标记为mask,用每个数在该位上的取值0/1作为分part的依据。
      将每个num & mask,根据a&1 = a、a&0 = 0的性质可知求出结果为num在mask为1的那位上的值,在该位上为0的在A部分,该位上位1的在B部分。因为重复的数字在该位上一定是一样的满足了分part要求②,而不重复的a和b在该位上一定是不同的,所以被分别分在了A和B两部分,满足了分part要求①。

示例: 以nums = [4,1,4,6]为例:

  1. 全部异或求出a ^ b:restmp = 4 ^ 1 ^ 4 ^ 6 = 1 ^ 6 = 0111(7)
    异或结果

  2. 求标记位mark(异或结果中最低位出现的1,表示a、b不同):mark = 0001
    可以用restmp & (-restmp) = mask
    也可以依次判断restmp每一位是否为1,mask不断左移来求出restmp最右边(最低位)的1
    在这里插入图片描述

  3. 分part: 以mask = 0001,即最低位的1作为分part标准把{4,1,4,6}分为两部分A和B。
    A部分:mark & num = 0,说明num的这一位是0:xxx0;{4,4,6}
    B部分:其他的num就是这一位是1的:xxx1。{1}
    根据标记位分part

  4. 分别求A、B部分的元素异或结果
    A:a = 4 ^ 4 ^ 6 = 6
    B:b = 1
    故所求结果为{1,6}

时间复杂度:O(n)
空间复杂度:O(1)

java实现

class Solution {
    public int[] singleNumbers(int[] nums) {
        //求出全体异或的结果,即为不重复的两个数组的异或结果
        int restmp = 0;
        int a = 0, b = 0;
        for(int num:nums){
            restmp^=num;
        } 
        //已知a^b=restmp 怎么从两个数的异或结果中分离出这两个数a和b
        //restmp中可以求出至少有一位是不同的即为1,用这个1来将原数组中的每个元素分为两部分
        //一部分是该位是1的,一部分是该为是0的。而因为a和b在该位上异或结果是1说明是不同的,所以他肯定被分在了这两部分的不同的部分中,而其他重复的数字在该位上肯定是相同的,要么是1,要么是0,所以肯定在相同的部分
        //求用来判断的最低位上的不同,即restmp从低位开始出现的第一个1,用&的性质判断
        int mask = restmp & (-restmp);
        // while((restmp&mask) == 0){//说明当前mask为1的位上,restmp为0,则寻找下一位
        //     mask <<=1;//左移一位
        // }

        //找出mask后,将nums所有元素分为两部分,两部分再分别异或,两部分的异或结果就是所求两个不重复的数字a和b
        for(int num:nums){
            if((mask&num) == 0){//当前num在mask这位上为0
                a ^= num;
            }else{
                b ^= num;
            }
        }
        return new int[]{a,b};
    }
}

结果

参考:力扣官方题解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值