题目:
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:输入:nums = [4,1,4,6] 输出:[1,6] 或 [6,1]
示例 2:输入:nums = [1,2,10,4,1,4,3,3] 输出:[2,10] 或 [10,2]
限制:2 <= nums.length <= 10000
解题思路:
首先,根据题目要求时间复杂度为O(N),空间复杂度O(1),因此排除暴力法(时间复杂度O(N^2))和哈希表统计法(时间复杂度O(1))。
本题考查的是位运算中的异或,首先来看看异或的理论知识:
- 异或的性质:两个数字异或的结果a^b是将a和b的二进制每一位进行运算,得出的数字。运算的逻辑是如果同一位的数字相同则为0,不同则为1;
- 异或的规律:任何数和本身异或则为0,任何数和0异或是本身;
- 异或满足交换律:即a^b^c,等价于a^c^b。
另外,主站136、137和645,也是考查位运算。
- 移位运算:左移位<<:各二进制位全部左移指定的位数,操作数移出左边界的位被屏蔽,从右边开始用0填补空位。例如9的二进制表示为00001001,它向左移动两位变为00100100,即36,相当于乘2^2,就是9*2^2,结果还是36.右移位>>:和左移位相反。
- 与运算&:两个同时为1则结果为1,否则为0;或运算|:两个其中有一个为1则为1。
解题步骤:
1.遍历nums执行异或:设整型数组nums中出现一次的数字为x和y,出现两次的数字为a,a,b,b,...,即:nums=[a,a,b,b,...,x,y],对nums中所有数字执行异或运算,留下的结果则为出现一次的数字x,即:
2.循环左移计算m:
- 根据异或运算定义,若整数
某二进制位为1,则x和y的此二进制位一定不同。也就是说,找到
某为1的二进制位,即可将数组nums拆分为上述的两个子数组。根据与运算特点,可知对于任何整数a有:a&0001=1,则a的第一位为1;若a&0010=1,则a的第二位为1;以此类推。
- 初始化一个辅助变量m=1,通过与运算从右向左循环判断,可获取整数
首位1,记录于m中。
3.拆分nums为两个子数组
4.分别遍历两个子数组执行异或:通过遍历判断nums中各数字和m做与运算的结果,可将数组拆分为两个子数组,并分别对两个子数组遍历求异或,则可得到两个只出现一次的数字。
5.返回值:返回只出现一次的数字x,y即可。
具体图解过程如下:
代码:
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
x, y, n, m = 0, 0, 0, 1
for num in nums: # 1. 遍历异或
n ^= num
while n & m == 0: # 2. 循环左移,计算 m
m <<= 1
for num in nums: # 3. 遍历 nums 分组
if num & m: x ^= num # 4. 当 num & m != 0
else: y ^= num # 4. 当 num & m == 0
return x, y # 5. 返回出现一次的数字
复杂度:
- 时间复杂度:O(N),线性遍历nums使用O(N)时间,遍历
二进制位使用O(32)=O(1)时间;
- 空间复杂度:O(1),辅助变量a,b,x,y使用常数大小额外空间。