剑指offer56-I.数组中数字出现次数(位运算)

该博客介绍了如何在O(N)的时间复杂度和O(1)的空间复杂度下,通过位运算解决找出整型数组中两个只出现一次的数字的问题。解题思路涉及异或运算的性质,如任何数与0异或是本身,以及异或满足交换律。通过遍历数组执行异或运算,然后循环左移计算标志位,将数组拆分为两个子数组,再分别对子数组求异或,最终得到只出现一次的数字。代码示例展示了具体的实现过程。
摘要由CSDN通过智能技术生成

题目

一个整型数组 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,即:​​​​​​​a\oplus a\oplus b\oplus b\oplus \cdots \oplus x\oplus y =0\oplus 0\oplus \cdots \oplus x\oplus y =x\oplus y

2.循环左移计算m:

  • 根据异或运算定义,若整数x\oplus y某二进制位为1,则x和y的此二进制位一定不同。也就是说,找到x\oplus y某为1的二进制位,即可将数组nums拆分为上述的两个子数组。根据与运算特点,可知对于任何整数a有:a&0001=1,则a的第一位为1;若a&0010=1,则a的第二位为1;以此类推。
  • 初始化一个辅助变量m=1,通过与运算从右向左循环判断,可获取整数x\oplus y首位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)时间,遍历x\oplus y二进制位使用O(32)=O(1)时间;
  • 空间复杂度:O(1),辅助变量a,b,x,y使用常数大小额外空间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值