LeetCode 只出现一次的数字(136,137,260)

只出现一次的数字:
136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

输入: [2,2,1]
输出: 1

这个很简单,只需要不断异或就可以了。最终结果就是答案,因为相同的数字异或为0。

137.只出现一次的数字 II
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

输入:nums = [2,2,3,2]
输出:3

这个题目就没有办法使用单纯的异或了,为了实现线性的时间复杂度和常数的空间复杂度,不使用hash。可以先观察一下,这个问题和第一题的不同,主要在于多了一个重复的元素。异或可以消除两个重复元素,那么可以设置一个新的操作来消除三个的重复元素吗?这需要拥有一点数字逻辑的知识。

要实现线性的时间复杂度和常数的空间,需要的还是位操作。按位考虑,假设k位的所有数组中的元素的1有个N个。N整除3的结果不是0就是1,如果是0,那么意味着只出现一次的数字对应的这位为0,反之为1。

如果是按位数考虑,需要记录的有三个状态,0,1,2。所以单纯的异或是不行的,我们需要用两位二进制来表示一位数字。

abxa* b*
00000
00101
01001
01110
10010
10100

这里使用ab来记录要求的只出现一次的数字对应位置的当前状态,当其遇到1时,便加1,如果遇到三个1,就回去0。这上面的分析是一样的,主要就是要表示每一位碰到1时的状态变化。这样可以根据这个真值表,写出逻辑表达式:

a* = (~a & b & x) | (a & ~b & ~x)
b* = ~a & (bˆx)

所以代码如下:
a, b就用来记录需要求的只出现一次的数字的每一位的对应的状态。但是注意,在最终结果的时候是不存在ab=10,这种状态的,因为这表示有个数字出现了两次,所以只有ab={00,01}这两种状态,所以可以直接用b来表示最终的数字。

a, b = 0, 0
for num in nums:
    a, b = (~a & b & num) | (a & ~b & ~num), ~a & (b ^ num)
return b

260.只出现一次的数字 III
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

与上题不同的是,这次的只出现一次的元素有两个了。假设是a和b,对所有元素进行异或,那么得到的是a^b。那么怎么样能得到a和b呢?
如果能把a和b分到两部分去,在这两部分分别做异或,就可以得到a和b了。那么如何分成两部分?考虑a^b一定是一个不为0的数字,那么问题来了,有一位是为1的,那么那一位之所以为1,就是因为a和b对应的那位分别是0和1。这就给分成两部分带来了方法,根据这位是0还是1,可以将所有的元素分成两部分。a和b分别被分到了不同的部分,在这两部分分别做异或,最终得到的就是a和b的值。

代码如下:其中x记录a^b的值,之后做了一步x = x & -x。因为-x的二进制是取反加一,那么和x一与,就能得到x的最低位的1。之后对数组里的所有元素进行遍历,如果num & x == 0便是对应的位置为0,用a记录。

x = 0
for num in nums:
    x = x ^ num
x = x & -x
a, b = 0, 0
for num in nums:
    if num & x == 0:
        a = a ^ num
    else:
        b = b ^ num
return [a, b]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值