题目
一个整型数组 nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路
在LeetCode上看了很多大佬的解答终于明白了,也帮助自己好好学习了一下位运算,这里记录一下,然后举一个栗子也可以帮助自己更好地理解
如果没有时间复杂度或空间复杂度的要求,可以考虑排序,或者使用字典来保存每一个元素。但是这里有时间和空间复杂度是要求,需要考虑使用位运算中的间或运算。
计算机中的数字都是以二进制的形式进行保存的,位运算是对二进制的运算,而间或是指对两个数字的二进制进行一一比对,如果数字相同,则结果为0,如果不同,则数字为1.以a=60,b=13为例,a的二进制是111100,b的二进制是1101,a有六位,b有四位,不足的用0补齐,所以把b二进制写成001101,然后一一比对。这里我们从右往左比对,a的最右边的数字是0,b的最右边的数字是1,不一样,所以根据异或的规则,异或结果的最后一位是1,然后比对倒数第二位,a和b倒数第二位都是0,相同,所以结果的倒数第二位是0,类似地可以得到这两个数异或的结果为110001,结果是49.
关于异或,有下面的几个性质
1.任何数和本身异或是0
2.任何数和0异或是本身
3.异或运算满足交换律,即a^b^c=a^c^b
利用以上三条性质,就可以来做出这道题,首先我们用0和nums
的每个元素做异或运算,根据以上的三条性质我们可以知道,最终的异或的结果是那两个只出现一次的数字的异或的结果,因为剩下的数字每个数字都出现了两次,再根据交换律,可以把相同的数字组合在一起,而相同的数字的异或结果都是0。因为有两个数字只出现了一次,所以异或结果一定不是0,然后我们将异或结果的第一个1记下来,则那两个只出现了一次的数字在这一位上一定是不一样的,所以可以根据这一位的异或结果对原来的数组进行分类。
用一个栗子来说明可以更好地理解,假设nums = [1,2,10,4,1,4,3,3]
,这里最终的输出的结果是[2,10]
,首先假设ret=0
,去和数组里的每一个数字进行异或运算,最终输出的是2和10的异或结果,结果是8,二进制的编码为1000,然后用一个变量h=1
去找出ret
第一个1的位置,显然最后h=8=1000(二进制)
,这样那两个只出现一次的数字和h做&
运算肯定不一样,而且一定有一个是0,在这里h&2=8&2=0,h&10=8&10=8!=0
,这样根据这个结论就可以把数组分成两部分,一部分是和h
的和运算结果为0,另一部分是和h
的和运算结果不为0,2和10肯定不会在同一个组,剩下相同的数字肯定在同一个组,在做异或运算的时候会变成0.所以再对这两个分开的数组单独做一次异或运算就可以得到那两个只出现一次的数字.
代码
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
ret, a, b = 0, 0, 0
for num in nums:
ret ^= num
h = 1
while(ret & h == 0):
h <<= 1
for n in nums:
if h & n == 0:
a ^= n
else:
b ^= n
return [a, b]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。