给定一个整数数组 nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例 :
输入: [1,2,1,3,2,5]
输出: [3,5]
注意:
- 结果输出的顺序并不重要,对于上面的例子,
[5, 3]
也是正确答案。 - 你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
解题思路
类似问题
Leetcode 136:只出现一次的数字(最详细的解法!!!)
Leetcode 137:只出现一次的数字 II(最详细的解法!!!)
首先想到的解法就是像137
问题一开始提出的那样,使用dict
。
class Solution:
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums_dict = {}
for num in nums:
nums_dict[num] = nums_dict.get(num, 0) + 1
result = list()
for key, val in nums_dict.items():
if val == 1:
result.append(key)
return result
但是如果使用O(1)
的空间复杂度要怎么做呢?我们很快想到通过位运算。一个非常简单的思路就是我们按照136
问题中的方法,直接对每个nums
的元素做xor
,最后我们得到的结果就是两个单一元素a
和b
的xor
。因为a
和b
不相同,所以它们之间必定会存在至少一个bit
不同,也就是说a xor b
的结果中至少有一个bit
是1
。我们从这么多的bit
中挑选出一个,然后其余位置为0
,那我们就构成了这样的一种mask
。例如
00...100
这样的mask
和a
和b
中元素与元素的话,必定有一个结果是0
,另外一个结果是mask
,这样我们就将a
和b
给分开了。那么问题就变成了,怎么构建这样的mask
?我们使用一个简单的策略就是a xor b
的最右边的1
作为flag
。那要怎么得到最右边的1
呢?这就涉及到补码的概念,我们知道负数在计算机中使用补码表示的,也就是反码加1
num:5
原码:0101
反码:1010
---------
num:-5
补码:1011
那么我们通过num&-num
就可以取出最右边的1
了。现在我们就可以遍历nums
然后,通过mask
就可以判断nums
中的那些元素的右边第一位是1
(根据上面例子),我们将这些数分成一类,将右边第一位是1
的数分成为另外一类,并且我们的a
和b
也就被分到不同的组中。这两组数字的个数不一定相同,但是最后一定是可以相互通过xor
消除,最后只剩a
和b
。
from functools import reduce
from operator import xor
class Solution:
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
mask = reduce(xor, nums)
mask &= -mask
result = [0]*2
for num in nums:
if num & mask:
result[0] ^= num
else:
result[1] ^= num
return result
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!