【LeetCode刷题】(260)只出现一次的数字III

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。

示例 :

输入: [1,2,1,3,2,5]
输出: [3,5]
注意:

结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

方法一:哈希表
建立一个值到频率的映射关系的哈希表,返回频率为 1 的数字。

算法:

from collections import Counter
class Solution:
    def singleNumber(self, nums: int) -> List[int]:
        hashmap = Counter(nums)
        return [x for x in hashmap if hashmap[x] == 1]

复杂度分析

时间复杂度:O(N)。
空间复杂度:O(N),哈希表所使用的空间。

方法二:两个掩码
本文将使用两个按位技巧:

使用异或运算可以帮助我们消除出现两次的数字;我们计算 bitmask ^= x,则 bitmask 留下的就是出现奇数次的位。

在这里插入图片描述
x & (-x) 是保留位中最右边 1 ,且将其余的 1 设位 0 的方法。

首先计算 bitmask ^= x,则 bitmask 不会保留出现两次数字的值,因为相同数字的异或值为 0。

但是 bitmask 会保留只出现一次的两个数字(x 和 y)之间的差异。
在这里插入图片描述
我们可以直接从 bitmask 中提取 x 和 y 吗?不能,但是我们可以用 bitmask 作为标记来分离 x 和 y。

我们通过 bitmask & (-bitmask) 保留 bitmask 最右边的 1,这个 1 要么来自 x,要么来自 y。
在这里插入图片描述
当我们找到了 x,那么 y = bitmask^x。

class Solution:
    def singleNumber(self, nums: int) -> List[int]:
        # difference between two numbers (x and y) which were seen only once
        bitmask = 0
        for num in nums:
            bitmask ^= num
        
        # rightmost 1-bit diff between x and y
        diff = bitmask & (-bitmask)
        
        x = 0
        for num in nums:
            # bitmask which will contain only x
            if num & diff:
                x ^= num
        
        return [x, bitmask^x]

另一篇非常清晰的讲解

我们之前做过 136 题 ,当时是所有数字都是成对出现的,只有一个数字是落单的,找出这个落单的数字。其中介绍了异或的方法,把之前的介绍先粘贴过来。

还记得位操作中的异或吗?计算规则如下。

0 ⊕ 0 = 0

1 ⊕ 1 = 0

0 ⊕ 1 = 1

1 ⊕ 0 = 1

总结起来就是相同为零,不同为一。

根据上边的规则,可以推导出一些性质

0 ⊕ a = a
a ⊕ a = 0
此外异或满足交换律以及结合律。

所以对于之前的例子 a b a b c c d ,如果我们把给定的数字相互异或会发生什么呢?

a ⊕ b ⊕ a ⊕ b ⊕ c ⊕ c ⊕ d
= ( a ⊕ a ) ⊕ ( b ⊕ b ) ⊕ ( c ⊕ c ) ⊕ d
= 0 ⊕ 0 ⊕ 0 ⊕ d
= d
然后我们就找出了只出现了一次的数字。

这道题的话,因为要寻找的是两个数字,全部异或后不是我们所要的结果。介绍一下 这里 的思路。

如果我们把原数组分成两组,只出现过一次的两个数字分别在两组里边,那么问题就转换成之前的老问题了,只需要这两组里的数字各自异或,答案就出来了。

那么通过什么把数组分成两组呢?

放眼到二进制,我们要找的这两个数字是不同的,所以它俩至少有一位是不同的,所以我们可以根据这一位,把数组分成这一位都是 1 的一类和这一位都是 0 的一类,这样就把这两个数分到两组里了。

那么怎么知道那两个数字哪一位不同呢?

回到我们异或的结果,如果把数组中的所有数字异或,最后异或的结果,其实就是我们要找的两个数字的异或。而异或结果如果某一位是 1,也就意味着当前位两个数字一个是 1 ,一个是 0,也就找到了不同的一位。

思路就是上边的了,然后再考虑代码怎么写。

怎么把数字分类?

我们构造一个数,把我们要找的那两个数字二进制不同的那一位写成 1,其它位都写 0,也就是 0…0100…000 的形式。

然后把构造出来的数和数组中的数字相与,如果结果是 0,那就意味着这个数属于当前位为 0 的一类。否则的话,就意味着这个数属于当前位为 1 的一类。

怎么构造 0…0100…000 这样的数。

由于我们异或得到的数可能不只一位是 1,可能是这样的 0100110,那么怎么只留一位是 1 呢?

方法有很多了。

比如,201 题 解法三介绍的 Integer.highestOneBit 方法,它可以保留某个数的最高位的 1,其它位全部置 0,源码的话当时也介绍了,可以过去看一下。

最后,总结下我们的算法,我们通过要找的两个数字的某一位不同,将原数组分成两组,然后组内分别进行异或,最后要找的数字就是两组分别异或的结果。

然后举个具体的例子,来理解一下算法。
[1,2,1,3,2,5]

1 = 001
2 = 010
1 = 001
3 = 011
2 = 010
5 = 101

把上边所有的数字异或,最后得到的结果就是 3 ^ 5 = 6 (110)

然后对 110 调用 Integer.highestOneBit 方法就得到 100, 我们通过倒数第三位将原数组分类

倒数第三位为 0 的组
1 = 001
2 = 010
1 = 001
3 = 011
2 = 010

倒数第三位为 1 的组
5 = 101

最后组内数字依次异或即可。

def singleNumber2(self, nums: int):
        # difference between two numbers (x and y) which were seen only once
        mask=0
        for num in nums:
            mask^=num
        temp=1
        res=[0,0]
        if (mask & 1)==1:
            pass
        else:
            temp=temp+1
        for y in nums:         
            if ((y & temp)==0):
                res[0]^=y
            else:
                res[1]^=y
        
        return(res)

进一步修改一下:
这里 提出了一个小小的改进。

假如我们要找的数字是 a 和 b,一开始我们得到 diff = a ^ b。然后通过异或我们分别求出了 a 和 b 。

其实如果我们知道了 a,b 的话可以通过一次异或就能得到,b = diff ^ a 。

public int[] singleNumber(int[] nums) {
    int diff = 0;
    for (int n : nums) {
        diff ^= n;
    }
    int diff2 = Integer.highestOneBit(diff);
    int[] result = { 0, 0 };
    for (int n : nums) {
        //当前位是 0 的组, 然后组内异或
        if ((diff2 & n) == 0) {
            result[0] ^= n;
        } 
    }
    result[1] = diff ^ result[0];
    return result;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值