java数据结构与算法刷题-----LeetCode645. 错误的集合(位运算解法需要重点掌握)

news2024/9/22 21:26:03
java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846

文章目录

    • 法一:桶排序思想
    • 法二:位运算

在这里插入图片描述

法一:桶排序思想

解题思路
  1. 题目说,每个集合的值都是1 ~ n,一般我们会想到将数组中元素,挨个作为key放入map中,然后遍历1~n从map中获取value,看看谁是0,谁是2.
  2. 但是我们可以直接再创建一个数组,长度为n+1,用下标来代表数字,将1~n的个数,放入桶中。比如遍历nums数组是,当前元素是1,就放入下标为1的桶中,此时这个桶有1个元素,当我们有遍历到1时,再次放入下标为1的桶,此时这个桶有2个元素。
    在这里插入图片描述
代码:时间复杂度O(n) 空间复杂度O(n)

在这里插入图片描述

class Solution {
    public int[] findErrorNums(int[] nums) {
        int[] ans = new int[2];//答案要求返回形式
        int[] bucket = new int[nums.length + 1];//桶排序的思想,因为nums中的值固定为1~n
        for (int num : nums) bucket[num]++;//将数组中的值,放入对应的桶
        for (int i = 1; i <= nums.length; i++) {//依次遍历1~n
            if (bucket[i] == 0) ans[1] = i;//如果当前桶中元素个数为0,说明集合缺少这个元素
            else if (bucket[i] == 2) ans[0] = i;//如果当前桶有2个元素,说明集合中这个值有重复
            if (ans[0] != 0 && ans[1] != 0) break;//如果已经找到答案,就无需继续遍历了
        }
        return ans;//返回答案
    }
}

法二:位运算

题目细节
  1. 每个集合包含的元素必然为1 ~ n。例如n = 4,那么集合可以是[1,2,3,4],[1,2,4,3],[4,3,2,1]等等,但是必然包含1,2,3,4,也就是1~n这n个数。
  2. 但是题目说了,每个集合都发生了错误,有一个数字重复,而另一个数字消失了,比如[1,2,2,4], 正确的应该是包含1,2,3,4这4个数,但是现在少了一个3.
位运算
  1. 异或
  1. 两数相同异或为0,两数不相同异或为1
  2. 任何数a异或0,都等于a本身。0 ⊕ a = a
  3. 两个相同的数异或必然为0。a ⊕ a = 0;
  4. 异或具有结合律和交换律。
  1. 结合律0⊕1⊕2⊕2 = (0⊕1)⊕(2⊕2) = 1 ⊕ 0 = 1;
  2. 交换律0⊕1⊕2⊕2 = 2⊕1⊕2⊕0
  1. 两个数都是1,相与为1. 1&1=1
  2. 两个数有一个是0,相与为0,1&0=0
  1. 补码
  1. C,C++,java等编程语言中,为了更好的和硬件交互,数字以补码形式存储。
  2. 各种码的转换关系如下,了解即可,我们只需要统一用补码进行计算即可。(看不懂没关系,继续看下面)
    在这里插入图片描述
    在这里插入图片描述
补码(了解即可)
  1. 真值:我们通过除基取余法得到的二进制代码,统一称为真值。例如十进制数8的二进制位1000,这个1000就是一个真值。
  2. 原码:那么如何区分真值是正数还是负数呢?我们只需要用掉开头的一个二进制位,0表示正数,1表示负数。例如8的原码就是0000 … 1000 标红的那位就是符号为,剩下的都是数值位. -8的原码就是1000 … 1000负数的符号位为1.
  3. 补码,方便计算机运算的一种码,它不方便人类理解,但是方便计算机。它可以通过原码来推导
  1. 正数的补码 = 原码
  2. 负数的补码 = 符号位不变,其余位取反,然后末位+1。当然我们有一个口诀,就是从右向左找到第一个1,然后将符号位和这个1之间所有元素按位取反即可(图解如下)
    在这里插入图片描述
这道题,需要你知道关于补码的什么呢?
  1. 集合中保存的都是1~n的正数,计算机保存也都是补码,也就是符号位为0表示正数
  2. 正数的补码和源码是一样的,例如1 = 0,000 0000 0000 0000 0000 0000 0000 0001 (以32位进行保存)
  3. 负数的补码和源码的区别是,符号位和最右边的1不变,这两个不变的二进制位中间的其余数值位全部取反
  1. 原码:例如-1= 1,000 0000 0000 0000 0000 0000 0000 0001
  2. 补码:例如-1= 1,111 1111 1111 1111 1111 1111 1111 1111
  1. 我们现在有了1和-1的补码。
  1. 1 = 0,000 0000 0000 0000 0000 0000 0000 0001
  2. -1= 1,111 1111 1111 1111 1111 1111 1111 1111
  3. 我们发现,除了最右边的1以外,这个1左边所有的数,都是不同的。
  1. 如果此时我执行1与-1 也就是 1 & (-1)

我会得到0,000 0000 0000 0000 0000 0000 0000 0001,也就是除了最右边的1以外,其余全是0. 这样我就得到了这个数的最低位的那个1.也就是我得到了这个数,最右边的一个二进制1的位置。并且其余二进制位全是0

  1. 得到它有什么用呢?作用就是简化判断条件,让我们只需要用if考虑两种情况,而不是无数种。
  1. 0 & 任何数都是0,只有1 & 1 才能唯一的 = 1. 这就是它的作用。对于最终得到的只有最右1,其余全为0的二进制串lowbit = 0,000 0000 0000 0000 0000 0000 0000 0001来说,只有遇到一个同样1在最右边的数才会不为0,否则它必然为0.
  2. 它让任何数与其相与只有两种结果,要么为1,要么为0.而不是各种值。
  1. 例如 8 = 0,000 0000 0000 0000 0000 0000 0000 1000
  2. 和lowbit相与0,000 0000 0000 0000 0000 0000 0000 0001
  3. 结果为==0,000 0000 0000 0000 0000 0000 0000 0000
  1. 如果不进行只取最右边1的操作,直接随便两个数呢?

8的二进制补码为:0000 … 1000
9的二进制补码为:0000 … 1001
异或结果为:==== 0000 … 1000 这个值=8,不同的数,还有无穷多种结果
请你告诉我,我该如何写if语句,描述这大量的结果呢?我们当然希望只有0或者1两种状态,以方便我们写if语句。所以这就是只保留最右边的1,其余全部为0的作用。

解题思路

在这里插入图片描述

  1. 案例的补码
  1. 1的补码:0,000 0000 0000 0000 0000 0000 0000 0001
  2. 2的补码:0,000 0000 0000 0000 0000 0000 0000 0010
  3. 3的补码:0,000 0000 0000 0000 0000 0000 0000 0011
  4. 4的补码:0,000 0000 0000 0000 0000 0000 0000 0100
  1. 找到,缺少的数和重复的数的异或结果,记为xor。
  1. 整体异或消除重复的元素:1⊕2⊕2⊕4 = 1⊕0⊕4 = 1⊕4. 这里利用了异或的结合律和异或规律(相同的数异或 = 0)
  2. 和1~n异或获得重复的数和缺少的数的异或。1⊕4⊕1⊕2⊕3⊕4 = (1⊕1)⊕(4⊕4)⊕(2⊕3) = 2⊕3此时就是重复的数2,和缺少的数3的异或结果。记为xor
  1. 找到xor这个异或结果的最低位1. 上面说过,这个操作就是将if判断简化为只需要判断是0还是1,而不是无穷多种.

xor & (-xor) = 只保留最低位的1,其余全为0. 记为lowbit = 0,000 0000 0000 0000 0000 0000 0000 0001。这里很巧,这个例子的lowbit正好是1的补码。

  1. 这道题需要两个结果,一个是缺少的数,另一个是重复的数

我们这里常用的套路就是分成两组计算。因为我们上面分析过,任何数和lowbit相与,只有0和1两种结果。我们将与lowbit相与为0分为一组,让它和num1进行异或。与lowbit相与为1的分为另一组,让它和num2进行异或。

  1. nums数组中的值[1,2,2,4],依次和lowbit进行分组异或. 可以获得两个没有任何问题的数。也就是既不是丢失的,也不是重复的。
  1. 1&lowbit = 1, num2 ^=1 = 0⊕1=1. 首先是1这个数,与lowbit相与,发现值为1,将其分为1组,和num2进行异或
  2. 2&lowbit = 0, num1 ^=2 = 0⊕2=2. 然后2这个数,与lowbit相与,发现值为0,分到0组,和num1异或
  3. 2&lowbit = 0, num1 ^=2 = 2⊕2 = 0. 然后又是2这个数,与lowbit相与,发现值为0,0组异或
  4. 4&lowbit = 0, num1 ^=4 = 0⊕4 = 4. 最后是4这个数,与lowbit相与,发现值为0,0组异或
  5. 最终,num1 = 4,num2 = 1
  1. 然后和1~n,也就是1,2,3,4进行再次分组异或,找到两个有问题的数。也就是重复的,和丢失的
  1. 1&lowbit = 1, num2 ^=1 = 1⊕1 = 0.
  2. 2&lowbit = 0, num1 ^=2 = 4⊕2 = 6. 这个6是二进制转换过来的,大家可以自己用代码算
  3. 3&lowbit = 1, num2 ^=3 = 0⊕3 = 3.
  4. 4&lowbit = 0, num1 ^=4 = 6⊕4 = 2.
  5. 最终,num1 = 2,num2 = 3。但是到底谁是丢失的,谁是重复的,我们也不知道
  1. 再次进行nums数组[1,2,2,4]的遍历比对,看看num1是否保存的是重复的值,如果是,num1作为重复值,num2作为缺失值返回。否则num1作为缺失值,num2作为重复值返回
代码:时间复杂度O(n) 空间复杂度O(1)

在这里插入图片描述

class Solution {
    public int[] findErrorNums(int[] nums) {
        //异或 两个数相同异或为0,两个数不同异或为1
        //任何数a异或0,都等于a本身。0 ⊕ a = a 
        //两个相同的数异或必然为0。a ⊕ a = 0;
        //最关键的是,异或具有结合律。0⊕1⊕2⊕2 = (0⊕1)⊕(2⊕2) = 1 ⊕ 0 = 1; 
        int n = nums.length;
        int xor = 0;
        //整体异或消除重复的元素:1⊕2⊕2⊕4 = 1⊕0⊕4 = 1⊕4. 这里利用了异或的结合律和异或规律(相同的数异或 = 0)
        for(int num:nums) xor ^= num;
        //和1~n异或获得重复的数和缺少的数的异或。1⊕4⊕1⊕2⊕3⊕4 = (1⊕1)⊕(4⊕4)⊕(2⊕3) = 2⊕3此时就是重复的数2,和缺少的数3的异或结果。记为xor
        for(int i = 1;i<=n;i++) xor^=i;
        //xor & (-xor) = 只保留最低位的1,其余全为0. 记为lowbit = `0`,000 0000 0000 0000 0000 0000 0000 0001。这里很巧,这个例子的lowbit正好是1的补码。
        int lowbit = xor & (-xor);
        //用两个变量,分别记录这道题答案需要的两个值
        int num1 = 0, num2 = 0;//初始为0
        //我们这里常用的套路就是分成两组计算。因为我们上面分析过,任何数和lowbit相与,只有0和1两种结果
        for(int num:nums){
        		//nums数组中的值[1,2,2,4],依次和lowbit进行分组异或. 可以获得两个没有任何问题的数。也就是既不是丢失的,也不是重复的。
            if((num & lowbit)==0) num1^=num;
            else num2 ^= num;
        }
        //然后和1~n,也就是1,2,3,4进行再次分组异或,找到两个有问题的数。也就是重复的,和丢失的
        for(int i = 1;i<=n;i++){
            if((i & lowbit)==0) num1^=i;
            else num2^=i;
        }
        //再次进行nums数组[1,2,2,4]的遍历比对,看看num1是否保存的是重复的值,如果是,num1作为重复值,num2作为缺失值返回。
        for(int num:nums){
            if(num==num1) return new int[]{num1,num2};
        }
        //否则num1作为缺失值,num2作为重复值返回
        return new int[]{num2,num1};
    }
}

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

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

相关文章

imu_utils安装及标定教程

本文使用香港科技大学的imu_utils方差工具标定&#xff0c;首先将INDEMIND双目惯性模组静止放置三个小时。然后采集IMU数据&#xff0c;生成Allan方差数据&#xff0c;由图分析得到加速度和角速度的高斯白噪声和随机游走Bias误差。 系统配置 系统版本ubuntu18.04OpenCV3.4.13…

C/C++读写文件和stringstream类

目录 C处理文件打开文件两种函数的区别 读文件两种函数区别其它读操作的函数fgetc&#xff1a;从文件中读取一个字符fgets&#xff1a;从文件中读取一个字符串fscanf&#xff1a;按格式从文件中读取指定内容&#xff0c;与scanf函数类似 写文件其它的常用写操作函数fputc&#…

【LeetCode-135】分发糖果(贪心)

LeetCode135.分发糖果 题目描述 老师想给孩子们分发糖果&#xff0c;有 N 个孩子站成了一条直线&#xff0c;老师会根据每个孩子的表现&#xff0c;预先给他们评分。 你需要按照以下要求&#xff0c;帮助老师给这些孩子分发糖果&#xff1a; 每个孩子至少分配到 1 个糖果。…

js中找出两个数组中不同的元素

文章目录 一、题目二、方法2.1、方法一2.2、方法二2.3、方法三 三、最后 一、题目 两个数组 var A [1, 5, 6]; var B [2, 6, 7]&#xff0c;实现一个方法&#xff0c;找出仅存在于A 或者仅存在于B中的所有数字 二、方法 2.1、方法一 const filterArr (arr1, arr2) > …

5大免费代理IP合集,你的代理IP该换啦!

一连代理 代理IP提供平台&#xff0c;代理IP覆盖HTTP/HTTPS/SOCKS5协议&#xff0c;涵盖直连和隧道代理。一键操作可以随机更换IP&#xff0c;实现高效稳定的网络代理。支持在PC、iOS和安卓等平台上使用。当前免费试用选项&#xff0c;让用户能够在使用之前先了解服务的性能和效…

归并排序模板

模板在文末&#xff0c;以下步骤方便理解记忆。 先贴一张快速排序模板步骤&#xff0c;用于对比记忆 归并排序步骤&#xff1a; &#xff08;0&#xff09;如果数组左边界L ≥ 数组右边界&#xff0c;则不需要排序&#xff0c;直接return。 &#xff08;1&#xff09;直接取…

力扣第92题——反转链表 II(C语言题解)

题目描述 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1…

成绩等级分数段查询(python条件分支语句match...case...)

根据有效分数序列及等级差值&#xff0c;计算并打印等级相应分数区间。 (笔记模板由python脚本于2024年01月20日 23:57:32创建&#xff0c;本篇笔记适合会条件分支语句的初学者的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&…

python:socket基础操作(2)-《udp发送信息》

基础发送udp信息 1.导入socket模块 2.使用udp模块 3.发送内容 4.关闭套接字 很简单的4步就可以实现udp的消息发送 import socket # 导入模块udp_socket socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 使用ipv4 udp协议udp_socket.sendto(b"hello world",(&…

翻毛皮鞋脏了不会清洗怎么办?资深劳保鞋厂家来教你

劳保鞋皮面材质中除了常见的牛皮材质&#xff0c;翻毛皮也是频繁使用的材料&#xff0c;材质不同&#xff0c;在养护上也有区别&#xff0c;今天百华小编来和大家聊聊翻毛皮材质的鞋子清洁方法。 翻毛皮鞋清洗前的准备工作 1.除灰&#xff1a;对于表面灰尘&#xff0c;可以使用…

C语言第六弹---分支语句(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 分支语句 1、 逻辑操作符&#xff1a;&& , || , &#xff01;4.1、 逻辑取反运算符 &#xff01;4.2、 与运算符4.3、 或运算符4.4、 练习&#xff1a;闰…

磁悬浮人工心脏的不良事件分析:美国FDA数据库的启示

引言&#xff1a; 左心室辅助装置&#xff08;LVAD&#xff09;是治疗末期难治性心力衰竭&#xff08;HF&#xff09;患者的有效手段。磁悬浮人工心脏HeartMate-3&#xff08;磁悬浮人工心脏&#xff09;作为第三代LVAD&#xff0c;自2017年获得美国食品药品监督管理局&#x…

pygame入门学习(四)位图的使用

大家好&#xff01;我是码银&#x1f970; 欢迎关注&#x1f970;&#xff1a; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 载入图片 pygame.image.load( )&#xff0c;Pygame 可以通过pygame.image.load( )函数处理位图文件。 大致可以支持以下文件&#xff1a;JPG…

怎么给wordpress网站底部页脚添加备案号和链接?

以前“WordPress后台 >> 常规”最底部是有一个ICP备案号的&#xff0c;我们只需要填写备案号并保存更改即可让WordPress自带主题底部显示ICP备案号&#xff0c;但是现在新版本的WordPress已经没有了这个ICP备案号选项&#xff0c;而且也无法直接添加公安联网备案号&#…

前端基于XLSX实现数据导出到Excel表格,以及提示“文件已经被损坏,无法打开”的解决方法

文章目录 一、vue实现导出excel1、前端实现1、安装xlsx依赖2、引入3、方法4、使用4.1、将一个二维数组转成sheet4.2、将一个对象数组转成sheet4.3、合并单元格4.4、一次导出多个sheet 5、支持的文件格式 2、后端实现 二、导出文件损坏1、前端请求导出接口&#xff0c;增加返回类…

对称二叉树,力扣

题目地址&#xff1a; 101. 对称二叉树 - 力扣&#xff08;LeetCode&#xff09; 难度&#xff1a;简单 今天刷对称二叉树&#xff0c;大家有兴趣可以点上面链接&#xff0c;看看题目要求&#xff0c;试着做一下。 题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c;…

区块链社交:Facebook在去中心化时代的探索

随着区块链技术的崛起&#xff0c;数字社交领域也正迎来一场革命。Facebook&#xff0c;作为全球最大的社交媒体平台之一&#xff0c;不甘落后&#xff0c;积极探索区块链的应用前景。本文将深入探讨Facebook在去中心化时代对区块链社交的探索&#xff0c;以及这一探索可能引发…

控制项目进展

优质博文 IT-BLOG-CN 假如一个项目准备工作做的非常周详&#xff0c;现在要做的就是监督项目的进展情况&#xff0c;理想状况下事情应当进展的很顺利&#xff0c;但实际上我们会发现项目永远不会完全按照经计划执行&#xff0c;我们必须进行项目控制。也就是我们需要不断进行调…

基于springboot+vue的在线文档管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 背景和意…

[ACM学习]自上而下树形dp

问题引入 设置dp状态&#xff0c;相比于更容易出错的贪心更...不易出错。 状态设计 如果选择父结点&#xff0c;就会使孩子结点不能被选择&#xff0c;我们会多开一维的dp&#xff0c;用来标记该点是否被标记过。 以1点举例&#xff0c;f[1][0]为不选它的状态&#xff0c;那么…