当前位置: 首页 > news >正文

Leetcode——137 260找出只出现一次的数

文章目录

  • 找出只出现一次的数
    • 引入
    • Leetcode 260
    • Leetcode 137

找出只出现一次的数

对于数组中有一类题,即某些数据在数组中只出现一遍,需要我们找出,今天我们来看看这个类型的题。

引入

想必大家应该见过这么一道题:

现给定一个数组,这个数组里面只有一个数字出现一遍,其他的数据均出现两遍,请找出只出现一次的那个数字。

这种题是有很多方法做的:
1.暴力查找

直接从头开始定位,往后查找看是否有重复数据。如果有就定位下一个位置。反之返回当前的数据。这个方法很简单,也最好想。但是时间复杂度为O(N^2),当数据个数较多时效率较低。

2.进行统计排序
在学习了八大排序算法后知道了统计排序的优势。先对数据出现次数进行统计,然后对统计数组查找个数为1的那个数据即可。但是时间复杂度为O(N)。

所以我们就在想,有没有办法,能够在线性时间复杂度的情况下还能做到常数级别的空间。
3.答案就是使用我们不太熟悉的按位异或计算

在这之前复习一下按位异或的知识点:
按位异或^,即对数据的补码进行每一个比特位异或操作。
如果该位相同,结果为0,相异为1.

所以就得到如下几个结论:

表达式结果
0 ^ aa
a ^ a0
a ^ b ^ aa ^ a ^ b

所以对于上述数组,我们可以先用一个值int ans去与数组中所有元素进行异或操作。由表格我们知道,每两个一样的数据进行异或是会变成0,而0和其他数异或结果不变。所以当我们对数组中每个数据都进行异或的时候,如果数组中只有一个元素出现一次,那么ans的值一定就是只出现一次的那个数字。

Leetcode 260

原题链接:Leetcode 260
这题改动一下,一个数组中有两个数字只出现一次,其他都是出现两次,且要求要线性时间复杂度和常数级别的空间复杂度。

那我们再来考虑一下异或的操作,定义变量int Xornum = 0,假设数组中只出现一次的数据分别是x1x2,那么最后x的值就是x = x1 ^ x2

现在就需要想办法将x1x2Xornum = x1 ^ x2中分开。

但是这个思路比较难想到,我们一起来看学习一下(我也是学习的):

1.先找到Xornum这个数二进制序列的最低位的1
这里我们先不管原理,先就这么做,找到Xornum的最低位1。
(注意:所有的对二进制码的操作都是对数据的补码进行操作)

应该怎么找呢?
答案就是让Xornum-Xornum这两个数据进行按位与操作,我们举个例子来看看:
在这里插入图片描述
以五为例子,我们知道5的最低位1就是从右往左数第一个位。通过Xornum & -Xornum的操作我们成功的找到了其最低位1,并且这个序列其他位全为0。我们设Xornum & -Xornum得到的结果放在变量int one_pos中。

但是有一件事情要注意,这个Xornum的是int类型,范围在-231 ~ 231 - 1间。对于1 - 231 ~ 231 - 1之间的数据,直接用这个方法就可以求,因为是可以找到对应的相反数的二进制序列。而对于-231这个有符号整形的最小值则不能这么求。

我们之前学过,10000000是-128的补码,其实也是它的源码和反码。这是特殊情况。对于-231 来讲,10000000 00000000 00000000 00000000就是它的补码。最高位实在第一个位置的。所以对于Xornum == -231 的情况,需要特殊处理一下。

所以最后得到表达式int one_pos = (Xornum == INT_MIN) ? Xornum : Xornum & -Xornum,其中INT_MIN是有符号整形的最小值。

2.分类操作
然后就进行一个分类的操作:
我们得到了Xornum的最低位的那个1(one_pos的那个1),而Xornum又是由x1x2按位异或出来的。也就是说Xornum的那一位的1(假设是从右往左数第L位),其实是由x1x2从右往左数第L位按位异或出来的。

为了方便叙述,我们称从右往左第L位第L位
抑或出来为1,那么也就是说,x1的第L位和x2的第L位一定是不同的,一定是一个为0,一个为1。这还是很好理解的。

也就是说,x1x2的第L位和one_pos的第l位的情况是:一个相同那么另外一个就不相同。

那我们就可以依次进行分类了:(以其补码第L位是否和one_pos的第L位是否相同)
对于原数组中,出现两次的那些数据,不管它们被分在哪一类,两个数字是一样的,那肯定是一类,那就放在一起。而只出现一次的两个数据一定是被分在两类的。

3.对不同分类中的数据进行异或
然后我们把不同类的中的所有数据异或起来,那么出现两次的那些数据异或后直接变成0了。那么当不同分类中的数据异或完成后,剩下的自然是只出现两次的数据了。

我们可以举一个例子看看:
在这里插入图片描述
这样子就很轻松的将两个只出现一次的数据分离出了。

最后来看看代码实现:

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        vector<int> v(2, 0);
        int xornum = 0;
        for(int x : nums){
            xornum ^= x;
        }
        int INTMIN = (-1) * pow(2,31);
        int one_pos = (xornum == INTMIN) ? xornum :xornum & (-xornum);
        for(int x : nums){
            if(x & one_pos)
                v[0] ^= x;  
            else
                v[1] ^= x;
        }
        return v;
    }
};

Leetcode 137

原题链接:Leetcode 137

这题对于引入的例子也是修改了一点:只有一个数字出现一次,其他的会出现3次,找出只出现一次的数据,并且要求线性时间复杂度,常数级别空间复杂度。

这题我们再来延续使用以下异或的思想,我们会发现很困难。因为其他数据出现三次,而异或操作只能抵消偶数个。如果按照刚刚的思想来分类也是不太可能的,因为分类的情况下,可能出现一次的数据和出现三次的数据会被分到一块,这是很难办的。

有没有别的办法呢?
答案是有的,我们可以按位处理。

设只出现一次的数据位int ans = 0;。假设我们从低位到高位求出ans的比特位:

对于每一位,无非就是0或者1,因为只有一个数据出现一次,其他的出现三次。那么其他的数据在该位上出现的0或者1一定是3的整数倍次。

很好理解,假设数组[3, 3, 3, 4, 4, 4, 1],假设当前求的是第一位(从右往左数),3的第一位是1,出现三次,4的第一位是0,出现三次。很明显都是三的倍数。而1的第一位是1,出现一次。所以整个数据第一位出现1四次,出现0三次。

以此类推,我们很容易得知:对于整个数组来说,第i位出现的0和1的次数一定是一个为3的倍数,一个比3的整数倍还多一个。多出来的那一个可以用取模操作得到是什么。那么就是ans的第i为的二进制位。

而一个int类型数据也就是32个比特位,所以只需要求32次ans的对应比特位即可。这个时间复杂度是线性的。

然后就是要将二进制位依次填充到ans中:
这个应该如何操作呢?

我们是从右往左依次获取二进制位的。
我们来找一下规律:
在这里插入图片描述
以此类推,只需要执行上述操作32次即可。哪怕第i位是0也是执行上述操作,只不过是把0进行移位后再按位或。

所以来看看代码实现:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        //确定每一个二进制位
        int ans = 0;
        //求单独出现的数的二进制补码序列 逆序
        int i = 0;
        while(i < 32){
            int OneNum = 0;
            for(int x : nums){
                if((x >> i) % 2) ++OneNum;
            }
            OneNum %= 3;
            ans = ans | (OneNum << i); 
            ++i;
        }        
        return ans;
    }
};

这里非常巧妙的求出第i位二进制序列是1还是0。算出数组中所有数字第i位1的出现次数(Onenum),如果是3的倍数,那么ans的这一位就是0,正好是Onenum取余3的结果。反之如果不是3的倍数,那么ans这一位就是1,也正好是Onenum取余3的结果。

至此就将这题也讲解完了。

相关文章:

  • 【密码学——基础理论与应用】李子臣编著 第六章 祖冲之序列密码 课后习题
  • Trae 下安装 Pylance 插件(仅作为实验,版权由微软所有)
  • 【lerobot】3-开源SO-100 主从臂的舵机位置校正、遥控操作(ubuntu系统)
  • 基于图扩散小波的连接组分析:定位结构-功能映射中的扩散源
  • docker部署GPUStack【Nvidia版本】
  • 【Hot100】239. 滑动窗口最大值
  • Express中间件(Middleware)详解:从零开始掌握(4)
  • 高级java每日一道面试题-2025年4月07日-微服务篇[Nacos篇]-如何监控Nacos的运行状态?
  • 深入探究AI编程能力:ChatGPT及其大规模模型的实现原理
  • 纯PHP编写的聊天室无需数据库,上传虚拟空间就可使用
  • 【PostgreSQL教程】PostgreSQL 特别篇之 语言接口连接PHP
  • [LeetCode 45] 跳跃游戏2 (Ⅱ)
  • Python高级爬虫之JS逆向+安卓逆向1.4节:数据运算
  • Solidity私有函数和私有变量区别,私有变量可以被访问吗
  • 在轨道交通控制系统中如何实现μs级任务同步
  • KiCad 9.0:如何在 PCB 上暴露铜皮(开窗)
  • go中new和make有什么异同?
  • [LeetCode 1306] 跳跃游戏3(Ⅲ)
  • AD利用转换工具快速生成异形焊盘
  • 从Ampere到Hopper:GPU架构演进对AI模型训练的颠覆性影响
  • 大理杨徐邱再审后上诉案将于下周开庭:案发已逾32年,故意杀人罪去年被撤销
  • 四川省委统战部副部长(正厅级)张荣履新峨眉电影集团“一把手”
  • 精细喂养、富养宠物,宠物经济掀起新浪潮|私家周历
  • 特朗普:乌克兰问题谈判短期内若无进展美将不再斡旋
  • 祥源文旅:2024年营业收入约8.64亿元,今年旅游经济总体预期更为乐观
  • 人民日报钟声:经济霸凌损害美国国家信誉