题目地址:https://leetcode.com/problems/single-number-iii/description/
题目的原意大致就是 有一个数组,里面的数字都是成对出现的,只有两个元素是只出现了一次,要求我们把这两个数字找出来。
这题一个效率比较高的解法是利用位运算来解
我们可以先把题目简化,我们假设一个数组,里面的数字都是成对出现的,但是有且仅有一个数字出现了一次,那我们怎么把这个只出现了一次的数字找出来呢?
我们需要利用抑或的两个特点:
1、任意数与0抑或结果都是他本身
2、相同的两个数异或的结果是0
那我们只要将数组里面的值全部参与一遍异或那最后的结果就是只出现了一次的数字,代码如下:
ret = 0
for i in nums:
ret ^= i
return ret
但是本题里面是有两个元素出现了一次,那我们应该怎么解决呢?其实还是利用异或的两个特性,我们可以把数组分成两部分,两个只出现了一次的数字分别在不同的组,组里了其他的元素都是成对出现的,那么问题就迎刃而解了。但是问题是怎么能让这两个元素分在不同的组呢?
首先我们依然要将数组里面的数异或一遍。
假设数组为:[1,2,1,3,2,5] 那么异或一编的结果为 3^5=6 转化为二进制为110,我们假设整型只占一个字节,那么二进制为00000110,这代表着两个只出现了一次的数字(3和5)
他们的二进制第二位(从右边数)跟第三位的值是不同的,其他都是相同的。所以我们只要找到一个不同的位数与两个数做按位与的运算,必然有一个结果为0,例如:
我们可以取第二位为: 10 转化为10进制是2,2&5=0, 也可以取第三位为: 100 转化为10进制为4,4&5=0
但是我们该如何取到有一位不同的值呢,我们可以用补码的特点,取到最后为1的值。
我们知道计算机里面的位运算都是用补码来做运算的,我们通过上一步的异或得到了结果为6(00000110),我们只要与其相对应的负数-6(11111010)做按位与的计算就可以得到(00000010)。
6的原码为 00000110 反码为 00000110
-6的原码为 10000110 反码为 11111001
那么如果6与-6的反码做运算的话结果为0,但是计算机里面的运算都是以补码来做的,正数的补码是其本身,负数的补码为除符号位各位取反末位+1(反码+1),
那么我们只有将异或得到的结果与其相反的数做按位与的运算,就可以得到其取到最后为1的值,这样就可以分为两组了。代码如下:
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
xor = 0
for i in nums:
xor ^= i
last_diff = (-xor) & xor
ret = [0, 0]
for i in nums:
if i & last_diff:
ret[0] ^= i
else:
ret[1] ^= i
return ret