一、题目
剑指 Offer 56 - I. 数组中数字出现的次数(位运算的妙用)
一个整型数组 nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例1:
输入: [4,1,4,6]
输出: [1,6]或者[6,1]
示例2:
输入: [1,2,10,4,1,4,3,3]
输出: [2,10] 或 [10,2]
二、分析
1.方法1:排序后,暴力遍历
这种方法思路简单,但是时间复杂度较高。排序消耗O(nlogn),遍历消耗O(n),思路简单,但是不是最优解。
2.方法2:位运算
分析
- 我们知道异或运算有个特点,0和任何数异或是任何数本身,先记住这个结论。我们先将此题简化为一个整型数组中只有一个数字 x x x只出现了一次,其他数字 a i a_i ai 都出现了两次。数组定义如下 n u m s = [ a i , a i , x ] nums=[ a_i, a_i ,x] nums=[ai,ai,x]对于这个简化了的题,我们如果想到用位运算异或的性质,就很好求解了,直接将数组中所有的数值做异或运算,结果就是出现了一次的数 x x x。公式如下:
x = a i x o r a i x o r x x=a_i\quad xor\quad a_i\quad xor\quad x x=aixoraixorx
-
而这道题里面有两个数字 x , y x,y x,y出现了一次。 n u m s = [ a i , a i , x , y ] nums=[ a_i ,a_i, x, y] nums=[ai,ai,x,y]我们将其分隔为两部分 n u m s 1 , n u m s 2 nums1,nums2 nums1,nums2(如何分隔下面会细说),保证x,y分别在不同的部分中。然后转换为1.中将两部分用异或位运算就可以得到 x , y x,y x,y。公式如下:
x = a i x o r a i x o r x y = a k x o r a k x o r y x=a_i\quad xor\quad a_i\quad xor\quad x\\ y=a_k\quad xor\quad a_k\quad xor\quad y x=aixoraixorxy=akxorakxory3.如何将 n u m s nums nums 分成 n u m s 1 和 n u m s 2 nums1和nums2 nums1和nums2,同时还得保证 x ∈ n u m s 1 x\in nums1 x∈nums1 y ∈ n u m s 2 y\in nums2 y∈nums2。我们知道x和y一定不相同,假设其二进制第n位不同,两者异或的结果在第n为一定为1。我们只需要找到改位置。然后拿$1<<n $ 去和 x , y x,y x,y做与运算,根据结果0,1来将其分成两组。(两个运算结果一定一个为1,一个为0)
1 < < n & x 1 < < n & y 1<<n\quad \& \quad x\\ 1<<n\quad \& \quad y 1<<n&x1<<n&y
至于其他存在两个重复的数分组也是用这种方法,只要保证相同的数分在同一组就行。 比 如 a 1 , a 1 在 一 组 。 a 2 , a 2 在 一 组 比如a1,a1在一组。a2,a2在一组 比如a1,a1在一组。a2,a2在一组。
复杂度
- 时间:O(n):遍历 nums使用 O(n)时间,x⊕y 二进制位使用 O(32) = O(1)时间。
- 空间:O(1)
三、代码
1.方法1:排序后,暴力遍历
'''
题目:剑指 Offer 56 - I. 数组中数字出现的次数
描述:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。
请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
方法:排序后,暴力遍历
复杂度分析:时间:O(n^2):排序O(n^2)+遍历O(n)
空间:O(1):res,i
坑:异或运算,必须转换成二进制异或,然后求解。
比如4(100)xor 6(110)==2(010)
'''
@staticmethod
def singleNumbers(self, nums: List[int]) -> List[int]:
nums.sort()
res, i = [], 0
while i < len(nums):
if i == len(nums) - 1:
res.append(nums[i])
break
if nums[i] == nums[i + 1]:
i = i + 1
else:
res.append(nums[i])
i = i + 1
return res
2.方法2:位运算
'''
题目:剑指 Offer 56 - I. 数组中数字出现的次数
描述:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。
请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
方法:参考题解:位运算。通过全部异或得到(X xor Y)。在通过找到该结果第一个不同位m,通过m和nums
依次与运算。进行分组,结果为0在一组,结果为1在另一组。以便唯一不同的X,Y分在两组中。(因为m & X 和 m&Y结果必然不同,利用
该性质分组)
复杂度分析:时间:O(n):线性遍历 nums使用 O(N)时间,x⊕y 二进制位使用 O(32) = O(1)时间。
空间:O(1)
坑:异或运算,必须转换成二进制异或。比如4(100)异或6(110)不是等于1而是等于2(010)
'''
@staticmethod
def singleNumbersA(self, nums: List[int]) -> List[int]:
temp_res = res1 = res2 = 0
m = 1
# 将所有nums异或,结果为X ^ Y
for i in nums:
temp_res = temp_res ^ i
# 找到X ^ Y 中第一个结果为1的位置。将该值与X,Y分别与运算,用于将X,Y分到不同组别
# 分组后利用【只有一个唯一不同值的组内,全nums异或,可得到唯一不同值】性质,分别求出两个不同解
while True:
if m & temp_res == m:
break
else:
m = m << 1
for i in nums:
if m & i == 0: # 用于分组
res1 = res1 ^ i # 分组后利用【只有一个唯一不同值的组内,全nums异或,可得到唯一不同值】性质,分别求出两个不同解
else:
res2 = res2 ^ i
return [res1, res2]
四、总结
这道题用位运算真是妙蛙种子吃着妙脆角秒进米奇妙妙屋秒到家了。
位运算常见性质总结:
0 or 任何数等于其本身
0 xor 任何数等于其本身(本题用到性质)
1 and 任何数等于其本身(本题用到性质)
0 and 任何数等于0
1 or 任何数等于1
1 xor 任何数等于本身取反