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

news2025/4/17 9:49:23

文章目录

  • 找出只出现一次的数
    • 引入
    • 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的结果。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2335192.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenTiny使用指南

最近项目里用到了一个新的组件库——OpenTiny&#xff0c;但是官方文档的使用指南的描述很复杂&#xff0c;花了一些时间尝试才正常使用。下面是一个使用步骤的描述&#xff0c;可放心食用&#xff1a; 一、安装 TinyVue 组件库同时支持 Vue 2.0 和 Vue 3.0 框架&#xff0c;…

KingbaseES之KDts迁移SQLServer

项目适配迁移SQLServer至金仓,今天写写KDts-WEB版迁移工具迁移SQLServer至KingbaseES的步骤,以及迁移注意事项. SQLServer版本:SQLServer2012 KingbaseES版本:V009R004C011(SQLServer兼容版) --1.进入数据库客户端工具KDTS工具目录&#xff0c;启动KDts服务&#xff1a; [king…

代码随想录动态规划part02

动态规划part02 62.不同路径 代码随想录 视频讲解&#xff1a;动态规划中如何初始化很重要&#xff01;| LeetCode&#xff1a;62.不同路径_哔哩哔哩_bilibili 递归法 动态规划&#xff0c;当前状态是由上一个状态转化来的 这里初始化错误了&#xff0c;想法是对的右一和…

详解如何复现DeepSeek R1:从零开始利用Python构建

DeepSeek R1 的整个训练过程&#xff0c;说白了就是在其基础模型&#xff08;也就是 deepseek V3&#xff09;之上&#xff0c;用各种不同的强化学习方法来“雕琢”它。 咱们从一个小小的本地运行的基础模型开始&#xff0c;一边跟着 DeepSeek R1 技术报告 的步骤&#xff0c;…

Java集合框架 源码分析 迭代器 并发修改异常底层原理

迭代器 Java中的Iterator&#xff08;迭代器&#xff09;是集合框架中用于遍历容器元素的统一接口&#xff0c;提供了一种标准化的元素访问方式&#xff0c;无需依赖具体集合类型的实现细节。以下是其核心要点&#xff1a; 一、核心方法与使用步骤 获取迭代器 通过集合的 it…

Cannot find module ‘vue‘ or its corresponding type declarations

在使用vue3vite创建新的工程时&#xff0c;在新增.vue文件时会出现Cannot find module vue这个错误。 只需要我们在项目中的.d.ts文件中添加以下代码即可 declare module *.vue {import { defineComponent } from vue;const component: ReturnType<typeof defineComponent&…

【Python爬虫】详细工作流程以及组成部分

目录 一、Python爬虫的详细工作流程 确定起始网页 发送 HTTP 请求 解析 HTML 处理数据 跟踪链接 递归抓取 存储数据 二、Python爬虫的组成部分 请求模块 解析模块 数据处理模块 存储模块 调度模块 反爬虫处理模块 一、Python爬虫的详细工作流程 在进行网络爬虫工…

欧拉服务器操作系统部署deekseep(Ollama+DeekSeep+open WebUI)

​​一、解压并安装 Ollama​​ # 1. 解压文件&#xff08;默认会得到一个二进制文件&#xff09; tar -xzvf ollama-linux-amd64.tgz# 2. 将二进制文件安装到系统路径 sudo mv ollama /usr/local/bin/ sudo chmod x /usr/local/bin/ollama# 3. 验证安装 ollama --version链接…

#4 我们为什么使用物联网? 以及 物联网的整体结构

设备不物联是否可以&#xff1f; 答案 是可以的&#xff0c;从项目实战的角度&#xff0c;还是有很多包括分拣&#xff0c;控制&#xff0c;检测等应用是分立的&#xff0c;这个和成本&#xff0c;场景&#xff0c;客户接受度等因素有关。 局部看&#xff0c;一些系统的确很简…

3D版的VLA——从3D VLA、SpatialVLA到PointVLA(不动VLM,仅动作专家中加入3D数据)

前言 之前写这篇文章的时候&#xff0c;就想解读下3D VLA来着&#xff0c;但一直因为和团队并行开发具身项目&#xff0c;很多解读被各种延后 更是各种出差&#xff0c;比如从25年3月下旬至今&#xff0c;连续出差三轮&#xff0c;绕中国半圈&#xff0c;具身占八成 第一轮 …

linux Shell编程之循环语句(三)

目录 一. for 循环语句 1. for语句的结构 2. for 语句应用示例 (1) 根据姓名列表批量添加用户 (2) 根据 IP 地址列表检查主机状态 二. 使用 while 循环语句 1. while 语句的结构 2. while 语句应用示例 (1) 批量添加规律编号的用户 (2) 猜价格游戏 三. until 循环语…

C#容器源码分析 --- Queue<T>

Queue<T> 是 System.Collections.Generic 命名空间下的先进先出&#xff08;FIFO&#xff09;动态集合&#xff0c;其核心实现基于​​循环数组​​&#xff0c;通过维护头尾指针实现高效入队和出队操作。 .Net4.8 Queue<T>源码地址&#xff1a;queue.cs (microso…

ViT 模型讲解

文章目录 一、模型的诞生背景1.1 背景1.2 ViT 的提出&#xff08;2020年&#xff09; 二、模型架构2.1 patch2.2 模型结构2.2.1 数据 shape 变化2.2.2 代码示例2.2.3 模型结构图 2.3 关于空间信息 三、实验3.1 主要实验3.2 消融实验 四、先验问题4.1 归纳偏置4.2 先验or大数据&…

IntelliJ IDEA 中安装和使用通义灵码 AI 编程助手教程

随着人工智能技术的发展&#xff0c;AI 编程助手逐渐成为提升开发效率的强大工具。通义灵码是阿里云推出的一款 AI 编程助手&#xff0c;它能够帮助开发者实现智能代码补全、代码解释、生成单元测试等功能&#xff0c;极大地提升了编程效率和代码质量。 IntelliJ IDEA 是一款广…

FreeRTOS入门与工程实践-基于STM32F103(一)(单片机程序设计模式,FreeRTOS源码概述,内存管理,任务管理,同步互斥与通信,队列,信号量)

裸机程序设计模式 裸机程序的设计模式可以分为&#xff1a;轮询、前后台、定时器驱动、基于状态机。前面三种方法都无法解决一个问题&#xff1a;假设有A、B两个都很耗时的函数&#xff0c;无法降低它们相互之间的影响。第4种方法可以解决这个问题&#xff0c;但是实践起来有难…

can‘t set boot order in virtualbox

Boot order setting is ignored if UEFI is enabled https://forums.virtualbox.org/viewtopic.php?t99121 如果勾选EFI boot order就是灰色的 传统BIOS就是可选的 然后选中任意介质&#xff0c;通过右边的上下箭头调节顺序&#xff0c;最上面的应该是优先级最高的 然后就…

2025年第十六届蓝桥杯省赛C++ A组真题

2025年第十六届蓝桥杯省赛C A组真题 1.说明2.题目A&#xff1a;寻找质数&#xff08;5分&#xff09;3.题目B&#xff1a;黑白棋&#xff08;5分&#xff09;4. 题目C&#xff1a;抽奖&#xff08;10分&#xff09;5. 题目D&#xff1a;红黑树&#xff08;10分&#xff09;6. 题…

asp.net Kestrel 和iis区别

Kestrel 和 IIS 都是用于托管 Web 应用程序的服务器&#xff0c;不过它们在多个方面存在显著差异&#xff0c;下面为你详细分析&#xff1a; 1. 所属平台与跨平台能力 Kestrel&#xff1a;是.NET Core 及后续版本的一部分&#xff0c;具备跨平台特性&#xff0c;可在 Windows…

《植物大战僵尸融合版v2.4.1》,塔防与创新融合的完美碰撞

《植物大战僵尸融合版》是基于经典塔防游戏《植物大战僵尸》的创意同人改版&#xff0c;由“蓝飘飘fly”等开发者主导制作。它在保留原版核心玩法的基础上&#xff0c;引入了独特的植物融合机制&#xff0c;玩家可以将不同的植物进行组合&#xff0c;创造出全新的植物种类&…

[LeetCode 1696] 跳跃游戏 6(Ⅵ)

题面&#xff1a; LeetCode 1696 数据范围&#xff1a; 1 ≤ n u m s . l e n g t h , k ≤ 1 0 5 1 \le nums.length, \ k \le 10^5 1≤nums.length, k≤105 − 1 0 4 ≤ n u m s [ i ] ≤ 1 0 4 -10^4 \le nums[i] \le 10^4 −104≤nums[i]≤104 思路 & Code 重点&…