题干
面试题56 - I. 数组中数字出现的次数
难度中等92收藏分享切换为英文关注反馈
一个整型数组 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 <= 10000
基础知识
这道题需要对异或操作以及按位与或操作有所了解
-
异或操作(a异或b记为a ⨁ \bigoplus ⨁b)
即按位不进位加,遵循公式(1$\bigoplus 1 = 0 , 1 1=0, 1 1=0,1\bigoplus 0 = 1 , 0 0 = 1, 0 0=1,0\bigoplus 1 = 1 , 0 1 = 1, 0 1=1,0\bigoplus$0=0)
举个例子
2$\bigoplus 32 的 二 进 制 表 示 为 103 的 二 进 制 表 示 为 11 所 以 2 3 2 的二进制表示为 10 3 的二进制表示为 11 所以2 32的二进制表示为103的二进制表示为11所以2\bigoplus 3 的 低 位 为 : 0 3的低位为:0 3的低位为:0\bigoplus 1 = 1 , 高 位 为 : 1 1=1,高位为:1 1=1,高位为:1\bigoplus$1=0
所以2$\bigoplus$3=1
而经过异或操作,我们可以发现两个规律:
- 相同的数字之间异或值为零(所有位都相等)
- 任何数字与0异或都等于本身(任何数与0异或都为本身)
- 按位与操作(a与b记为a&b)
即按位进行逻辑与操作,遵循公式(1&1=1,1&0=0,0&1=0,0&0=0)
其实现过程与异或相似
引例
题干:在数组中,所有的数值都出现了两次,只有唯一一个数字出现了一次,找出这个数。
解答:将数组中所有的数值进行异或操作,由于相等的数字异或之后都会归零,零与任何数字异或都会得到该数字本身,所以最后异或的结果就是唯一的那个出现一次的数字。
思路
经过前面的例子,我们知道了可以利用异或操作找出唯一的出现一次的数值,本题与引例的区别就是需要找到两个不同的出现一次的数值,这个时候如果直接将所有的数值进行异或操作之后,得到的将是两个只出现一次的数字(记为a,b)进行异或的结果,无法直接区分。
这里我们可以利用上一道题的思想,针对本题,我们可以把数组分成两组,这两组要满足如下条件:
条件1:相同的数字在同一组中
条件2:只出现一次的两个数字不在同一组中
如果能够得到这样的划分,那么本题就可以使用引例的做法分别对两组数据进行操作就可以了。
这个时候需要明确两个概念:
- 对于两个相同的数字,他们对应二进制表示的每一位都是相同的
- 对于两个不同的数字,他们对应二进制表示则一定有不同的位
由于我们最终要找的两个数字a,b是不同的,所以这两个数字的二进制表示一定有不同的位,我们只需要把所有数字中这一位为0的划为一组,这一位为1的划为另一组,这个时候我们就发现这样划分的结果既可以使相同的数字在同一组中,又可以把a和b区分开。
此时我们只需要找到a和b的一个二进制表示不同的位就可以了
想找到这个位其实很简单,我们可以直接将数组中的数值进行一次异或操作,这样就可以得到res = a ⨁ \bigoplus ⨁b
而res中值为1的位,一定是a和b二进制表示不同的位(只有不同的数异或才会为1)
这样基本就大功告成了,最后一步,要找到res中值为1的位,
首先:计算res&(00000001), 若结果为1,则res最低为为1,若不是,则继续计算res&(00000010)判断第二位。以此类推,由于a!=b,所以一定会找到某一位值为1
这样整个流程就可以代码实现了
- python代码
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
res = functools.reduce(lambda x, y: x ^ y, nums)
mask = 1 # 记录a和b不同的位
while res & mask == 0:
mask <<= 1
a, b = 0, 0
for num in nums:
if num & mask:
a ^= num
else:
b ^= num
return [a, b]