【Java算法专场】位运算(下)

news2024/9/20 8:05:07

目录

判定字符是否唯一

​编辑 算法分析

算法步骤

算法代码

hash表

位运算 

两整数之和

算法分析 

算法步骤

算法代码

只出现一次的数字 II

算法分析

算法步骤

算法代码

只出现一次的数字 III

算法分析

算法步骤

算法代码

面试题 17.19. 消失的两个数字 

算法分析

算法步骤

算法代码


在前面,已经讲解了位运算的基础知识,那么这一篇就来讲算法题。

判定字符是否唯一

 算法分析

本道题就是要判断字符串中是否存在重复的字符,如果存在,那么就返回false,反之,返回true。我们可以使用hash表,可以让时间复杂度达到O(n),空间复杂度为O(1).但本道题有限制,就是不能使用额外的数据结构,所以我们可以使用位运算来进行。利用位图的思想。

算法步骤

  1. 预判断:我们都知道字符最多只有26个不重复的,如果超过了26,说明其中有字符重复,可以直接返回false。(根据鸽巢原理
  2. 初始化:我们可以定义一个bitmap并初始化为0。
  3. 遍历字符串:字符最多不超过26个,所以我们可以根据位图的思想,定义一个x,用来表示bitmap上的x处的比特位。如果有某个比特位上的二进制数按位与(&)1后不为0,说明字符串中存在着重复的字符,直接返回false。若不为1,说明不存在,并将此处的比特位赋值为1(bitmap|=(1<<x))。
  4. 返回结果:当遍历完字符串,若没有出现重复的字符,则直接返回true。

算法代码

hash表

   /**
         * 判断字符串中的字符是否都是唯一的
         *
         * @param astr 输入的字符串,由小写字母组成
         * @return 如果字符串中的字符都是唯一的,返回true;否则返回false
         */
        public boolean isUnique(String astr) {
            // 创建一个长度为26的数组,用于记录每个字母出现的次数
            int hash[]=new int[26];
            // 遍历输入的字符串
            for(int i=0;i<astr.length();i++){
                // 将字符转换为对应的数组索引
                int ch=astr.charAt(i)-'a';
                // 将对应索引的值加1,表示该字符出现了一次
                hash[ch]++;
                // 如果该字符出现的次数大于1,则字符串中存在重复字符,返回false
                if(hash[ch]>1) return false;
            }
            // 遍历完字符串后,没有发现重复字符,返回true
            return true;
        }

时间复杂度为O(n),空间复杂度为O(1)

位运算 

/**
         * 检查字符串是否所有字符唯一
         * 
         * @param astr 待检查的字符串
         * @return 如果字符串中的所有字符都是唯一的,则返回true;否则返回false
         */
        public boolean isUnique1(String astr) {
            // 如果字符串长度超过26,则不可能所有字符唯一(因为只有一个字母表)
            if(astr.length()>26) return false;
            
            // 使用位图来记录每个字符是否已经出现过,初始为0
            int bitmap=0;
            
            // 遍历字符串中的每个字符
            for(int i=0;i<astr.length();i++){
                // 将字符转换为a-z的索引(0-25)
                int x=astr.charAt(i)-'a';
                
                // 如果该位置在位图中已经被设置为1,表示字符重复,返回false
                if(((bitmap>>x)&1)==1) return false;
                
                // 将位图中对应位置设置为1,表示字符已经出现过
                else bitmap|=(1<<x);
            }
            
            // 字符串遍历完成后,没有发现重复字符,返回true
            return true;
        }

时间复杂度为O(n),空间复杂度为O(1)

两整数之和

算法分析 

本道题最简答的方法,我不用说都知道,但是这样不符合题意了hh。这道题我们可以使用位运算来进行。需要用到按位异或(^)、按位与(&)等操作。

我们拿第一个案例分析一下。

a=1,b=3

转为二进制就是

0000 0001

0000 0011

我们知道按位异或是无进位相加,那么我们按位异或就会得到

所以我们可以使用按位与(&),按位与的特点就是相同为1。所以当两个数按位与,得到二进制位为1的数,说明此处需要向左移一位。

将a^b的值给a,(a&b)<<1的值给b,重复以上操作,直到b为0,就可以得到结果。

依次类推。

算法步骤

  1. 初始化:定义变量digit1和digit2并初始化为0,digit1用来存储a^b的结果,digit2用来存储(a^b)<<1的结果。
  2. 循环:先让a^b并将结果存到digit1中,再(a&b)<<1存储到digit2中,将digit1的值给a,digit2的值给到b,重复上述操作,直到为0.
  3. 返回结果:此时我们返回a,就是我们a+b的和。

算法代码

/**
         * 计算两个整数的和
         * 该方法通过位操作实现加法,避免使用算术加法运算符
         * 
         * @param a 第一个整数
         * @param b 第二个整数
         * @return 两个整数的和
         */
        public int getSum(int a, int b) {
            // 初始化两个变量,用于存放每一步的无进位加法结果和进位
            int digit1=0,digit2=0;
            
            // 当第二个数不为零时,继续进行加法操作
            while(b!=0){
                // 通过异或操作计算无进位加法结果
                digit1=a^b;
                // 通过按位与操作和左移操作计算进位
                digit2=(a&b)<<1;
                // 将无进位加法结果赋值给a,将进位赋值给b,进行下一轮计算
                a=digit1;
                b=digit2;
            }
            // 当进位为零时,a中存放的就是最终的和
            return a;
        }

时间复杂度为O(1),空间复杂度为O(1).

只出现一次的数字 II

算法分析

 在上一篇中,我们讲过只出现一次的数字I,而本道题与这道题的不同点就在于是要在一个其他元素都出现3次的数组中找出现一次的数字。这道题我们需要用到位图的思想。

先来想下,对于每一个比特位来说,它可能出现的情况有4种:

  1. 有3n个0和一个0
  2. 有3n个0和一个1
  3. 有3n个1和一个0
  4. 有3n个1和一个1  

对于这4种情况,其实只有一个0或者1的数,就是a的比特位。我们可以看到,当把比特位上的数加起来之后,%3得到的依旧是只出现一次的比特位,所以我们可以判断每个数的第x个比特位上是否为1,若为1则加到一个计数器k中,当遍历完数组,如果k%3不为0,说明此处的比特位不为0,改为1即可.遍历32个比特位,重复上述操作即可。

算法步骤

  1. 初始化:定义变量ans初始化为0,用来存储只出现一次的数。
  2. 遍历:我们需要从第一个比特位开始遍历,定义k用来存储第x个比特位上1的个数,内循环遍历数组,同时判断第x位上是否为1.若为1则k+1。当遍历完数组后,判断k%3是否不为0,若不为0,则将x位上设置为1.重复上述操作。
  3. 返回结果:当遍历完之后,此时返回ans就是只出现一次的值。

算法代码

/**
 * 寻找数组中只出现一次的数字
 * 给定一个整数数组 nums,其中除一个数字之外,其他数字都出现三次
 * 找出那个只出现了一次的数字
 * 
 * @param nums 一个整数数组,其中除一个数字之外,其他数字都出现三次
 * @return 那个只出现了一次的数字
 */
public int singleNumber(int[] nums) {
    // 初始化答案变量
    int ans=0;
    // 从最高位开始,逐位检查数字中1的个数
    for(int x=31;x>=0;x--){
        // 初始化当前位1的个数变量
        int k=0;
        // 遍历数组中的每个数字
        for(int num:nums) {
            // 累加当前位上的1的个数
            k+=(num>>x)&1;
        }
        // 如果当前位1的个数不是3的倍数,则该位上的数字为1
        if(k%3!=0) ans|=1<<x;
    }
    // 返回最终结果
    return ans;
}

时间复杂度为O(n),空间复杂度为O(1)

只出现一次的数字 III

算法分析

本道题要求我们在数组中找到两个只出现1次的数字,我们可以使用hashset,但是hashset的时空复杂度为O(n),但本道题我们可以使用位运算来解决。

算法步骤

  1. 初始化:定义一个sum,用来消除两两出现的数字,存储出现1次的两个数。
  2. 遍历数组:遍历数组,将值按位异或到sum中。
  3. 定义变量:定义变量lsb同时初始化为sum(这里需要判断sum是否溢出,如果溢出要进行sum&(-sum)相当于取反),同时定义type1和type2用来存储出现1次的数字。
  4. 遍历数组:遍历数组,此时同时判断(lsb&x)的值,若为1,则让type1^=x,反之,则让type2^=x.
  5. 返回结果:当遍历完数组,此时new一个数组存储两个值即可。

算法代码

/**
         * 寻找数组中出现一次的两个数字
         * 该方法通过位操作实现,主要利用异或运算的性质来找到出现一次的两个数字
         * 
         * @param nums 输入的整数数组,其中除了两个数字出现一次外,其他数字都出现两次
         * @return 返回一个包含两个出现一次的数字的数组
         */
        public int[] singleNumber(int[] nums) {
            // 对数组中的所有数字进行异或运算,得到的结果是两个只出现一次的数字的异或结果
            int sum=0;
            for(int x:nums) sum^=x;
            
            // 找到sum中最低位的1,用于将数组分为两组,一组包含只出现一次的第一个数字,另一组包含只出现一次的第二个数字
            // 这样做是为了防止溢出,并确保能够正确分组
            int lsb=(sum==Integer.MIN_VALUE?sum:sum&(-sum));
            
            // 初始化两个数字,分别用来存放只出现一次的两个数字
            int type1=0; int type2=0;
            
            // 再次遍历数组,根据lsb是否为1,将数组分为两组,并分别对两组进行异或运算
            for(int x:nums){
                if((lsb&x)!=0){
                    // 如果lsb位为1,异或结果放入type1
                    type1^=x;
                }else{
                    // 如果lsb位为0,异或结果放入type2
                    type2^=x;
                }
            }
            
            // 返回两个只出现一次的数字
            return new int[]{type1,type2};
        }

面试题 17.19. 消失的两个数字

算法分析

这道题其实就是消失的数字+只出现一次的数组III的结合。

算法步骤

  1. 初始化:定义变量sum,并初始化为0
  2. 预处理:让sum按位异或(^)遍历数组元素,同时这里是缺了两个数,所以我们也让sum按位异或从1遍历到nums.length+2的数。从而可以找出缺失的两个数字的按位异或。
  3. 定义diff:这里我们定义一个diff,用来表示sum的比特位。
  4. 循环遍历:我们遍历sum的比特位,如果(((sum>>diff)&1)==1),说明找到了两数不同的位置,停止循环;反之,让diff++,
  5. 定义数组ret:这里我们定义ret数组来存储两个消失的值。
  6. 遍历:这里我们需要遍历nums数组和从1到nums.length+2的数字,根据异或的数不同来分成两类(全是0或者1),从而来找出不同的数.

算法代码

/**
         * 找到数组中缺失的两个数字
         * 使用异或运算来确定缺失的数字
         * 
         * @param nums 输入的数组,其中包含了N个数字,范围从1到N+2,有两个数字缺失
         * @return 返回一个包含两个缺失数字的数组
         */
        public int[] missingTwo(int[] nums) {
            // 初始化sum为0,用于异或运算
            int sum = 0;
            // 对数组中的每个数字进行异或运算,目的是找出缺失的两个数字的异或结果
            for (int num : nums)
                sum ^= num;
            // 对1到N+2的每个数字进行异或运算,其中N为数组长度
            // 目的是取消掉数组中出现的数字,最终得到的异或结果是两个缺失数字的异或结果
            for (int i = 1; i <= nums.length + 2; i++)
                sum ^= i;
            // 找到两个缺失数字的异或结果中的第一个为1的位,作为分组依据
            int diff = 0;
            while (true) {
                // 检查sum的第diff位是否为1
                if (((sum >> diff) & 1) == 1)
                    break;
                else
                    diff++;
            }
            // 初始化结果数组
            int[] ret = new int[2];
            // 根据diff位是否为1,将数组中的数字分为两组,并分别对两组数字进行异或运算
            for (int x : nums) {
                // 根据diff位是否为1来决定是哪一组
                if (((x >> diff) & 1) == 1)
                    ret[0] ^= x;
                else
                    ret[1] ^= x;
            }
            // 对1到N+2的每个数字进行同样的分组和异或运算
            for (int x = 1; x <= nums.length + 2; x++) {
                if (((x >> diff) & 1) == 1)
                    ret[0] ^= x;
                else
                    ret[1] ^= x;
            }
            // 返回结果数组,即两个缺失的数字
            return ret;
        }

时间复杂度为O(n),空间复杂度为O(1).


以上就是本篇的所有内容,位运算就先到这里了~ 

若有不足,欢迎指正~

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

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

相关文章

python 如何实现执行selenium自动化测试用例自动录屏?

做自动化测试已经好多年了&#xff0c;随着项目技术的正增长提升&#xff0c;我们也不断完善并提高自己的技术能力&#xff0c; 下面给大家分享一个 selenium 自动化执行测试用例的录屏功能。希望对大家有帮助&#xff01; 首先&#xff0c;我们为什么要执行自动化录屏功能呢…

TIOBE 8月编程排行榜出炉!Python 正在追赶 Java 的 TIOBE 指数记录

2024年8月的TIOBE编程语言排行榜出炉了&#xff01;对于开发者而言&#xff0c;这份排行榜不仅仅是编程语言的简单排序&#xff0c;更是行业趋势的风向标。而今年&#xff0c;Python的表现格外引人注目&#xff0c;它正在一步步逼近Java的TIOBE指数记录。让我们一起看看这两大编…

es的执行命令日志输出

1. 说明 最近项目对接es&#xff0c;使用的客户端为&#xff1a; <dependency><groupId>co.elastic.clients</groupId><artifactId>elasticsearch-java</artifactId><version>8.11.4</version></dependency>使用过程中希望能…

3-1 介绍及传感器(智能应用篇)

3-1 介绍及传感器&#xff08;智能应用篇&#xff09; 3-0 本章介绍3-1 传感器介绍 3-0 本章介绍 Arduino实际应用到生活中 科技创作给生活的乐趣与便利 前两部分内容为基础 太极创客官方网站 课程主角led Led蕴含丰富的科技知识 水立方&#xff0c;led 主机led 汽车le…

整理 酷炫 Flutter 开源UI框架 FAB

flutter_villains 灵活且易于使用的页面转换。 项目地址&#xff1a;https://github.com/Norbert515/flutter_villains 项目Demo&#xff1a;https://download.csdn.net/download/qq_36040764/89631324

前端开发攻略---一篇文章让你快速上手TypeScript

目录 1、TypeScript简介 2、为何需要TypeScript 2.1 今非昔比的JavaScript 2.2 JavaScript中的困扰 2.3 静态类型检查 3、编译TypeScript 3.1 命令行编译 3.2 自动化编译 4、类型声明 5、类型推断 6、类型总览 6.1 JavaScript中的数据类型 6.2 TypeScript中的数据类…

【模电笔记】——信号的运算和处理电路(含电压比较器)

tips&#xff1a;本章节的笔记已经打包到word文档里啦&#xff0c;建议大家下载文章顶部资源&#xff08;有时看不到是在审核中&#xff0c;等等就能下载了。手机端下载后里面的插图可能会乱&#xff0c;建议电脑下载&#xff0c;兼容性更好且易于观看&#xff09;&#xff0c;…

Python实战项目:天气数据爬取+数据可视化(完整代码)

一、选题的背景 随着人们对天气的关注逐渐增加&#xff0c;天气预报数据的获取与可视化成为了当今的热门话题&#xff0c;天气预报我们每天都会关注&#xff0c;天气情况会影响到我们日常的增减衣物、出行安排等。每天的气温、相对湿度、降水量以及风向风速是关注的焦点。通过…

搭建高可用OpenStack(Queen版)集群(十一)之OpenStack集成ceph服务

一、OpenStack集成ceph服务 一&#xff09;Openstack集成Ceph准备 1、OpenStack存储知识 1、OpenStack数据存储分类 Openstack环境中&#xff0c;数据存储可分为临时性存储与永久性存储。 临时性存储&#xff1a;主要由本地文件系统提供&#xff0c;并主要用于nova虚拟机的本地…

开放式耳机哪种好用又实用?五款爆火单品推荐

现在市面上的开放式耳机真的参差不齐&#xff0c;而且想要挑选到一款适合自己的开放式耳机&#xff0c;那真的很难&#xff0c;要做很多功课&#xff0c;现在购物就像是在大海捞针一样&#xff0c;所以我们该怎么挑选才能选到一款适合自己的开放式耳机呢&#xff1f;这个问题相…

数据库原理面试-核心概念-问题理解

目录 1.数据库、数据库系统与数据库管理系统 2.理解数据独立性 3.数据模型 4.模式、外模式和内模式 5.关系和关系数据库 6.主键与外键 7.SQL语言 8.索引与视图 9.数据库安全 10.数据库完整性 11.数据依赖和函数依赖 12.范式&#xff1f;三范式&#xff1f;为什么要遵…

Java基础11:JavaDoc生成文档

本节内容教学视频连接&#xff1a;https://www.bilibili.com/video/BV12J41137hu?p32&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5https://www.bilibili.com/video/BV12J41137hu?p32&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.什么是JavaDoc&#xff1f; Javado…

双向重发布实验

要求&#xff1a; 1. 如图搭建网络拓扑&#xff0c;所有路由器各自创建一个环回接 口&#xff0c;合理规划 IP 地址 2.R1-R2-R3-R4-R6 之间使用 OSPF 协议&#xff0c; R4-R5-R6 之间使用 RIP 协议 3.R1 环回重发布方式引入 OSPF 网络 4.R4/R6 上进行双点双向重发布 5. …

开发效率翻倍攻略!大学生电脑小白管理秘籍,资料秒搜技巧大公开!C盘满了怎么办?如何快速安全的清理C盘?烦人的电脑问题?一键解决!

如何正确管理自己的第一台电脑&#xff1f;大一新生如何管理自己的电脑&#xff1f;老鸟如何追求快捷操作电脑&#xff1f; 文章目录 如何正确管理自己的第一台电脑&#xff1f;大一新生如何管理自己的电脑&#xff1f;老鸟如何追求快捷操作电脑&#xff1f;前言初级基础分区操…

我开源了一个新项目! Product Hunt 每日中文热榜

Product Hunt 每日中文热榜 大家好&#xff0c;今天非常兴奋地与大家分享一个我刚刚开源的新项目 —— producthunt-daily-hot。这个项目从构思到实现仅花了 1 天时间&#xff0c;希望对大家有所帮助。Product Hunt 每日热榜 已上线&#xff0c;榜单会在每天下午 4 点自动更新…

学习记录——day30 网络编程 端口号port 套接字socket TCP实现网络通信

目录 一、端口号 port 二、套接字 socket 1、原理 2、socket函数介绍 三、TCP实现网络通信 1、原理 2、TCP通信原理图 3、TCP相关函数 1&#xff09;bind 绑定 2&#xff09;listen 监听 3&#xff09;accept 接收连接请求 4&#xff09;recv 接收 5&#xff09;sen…

GitHub的常用操作

目录 GitHub GitHub加速 克隆GitHub上的项目到本地 克隆GitHub上指定分支的项目 把本地项目上传到GitHub上管理 删除分支里的内容 单个仓库管理多个项目 上传项目到新建的分支 目前正在逐步熟悉GitHub&#xff0c;打算把整理好的代码上传到GitHub上&#xff0c;建立属…

EasyRecovery17中文版本重磅发布更新!带你三分钟了最新功能

“你到底删了我什么&#xff1f;&#xff1f;” “我只是把聊天记录清理了下……” “你知不知道里面还有我发你的工作资料啊&#xff01;&#xff01;” …… 每当听到这种对话&#xff0c;我就知道又有人因为删错文件而和另一半/同事/领导吵起来了。 毕竟在这个手机电脑不离手…

超声波清洗机哪个牌子好?四款公认最好的超声波清洗机分享

随着生活水平的提高&#xff0c;人们对于生活有了更高的要求&#xff0c;而我们平时经常使用的一些小产品也要经常清理保持干净&#xff0c;所以针对于这些小产品&#xff0c;超声波清洁机应运而生。超声波清洗机&#xff0c;可能很多小伙伴见过或者使用过&#xff0c;它主要利…

多模态大语言模型的免训练视觉提示学习 ControlMLLM

ControlMLLM: Training-Free Visual Prompt Learning for Multimodal Large Language Models github paper 在本研究中&#xff0c;提出了一种无需进行训练的方法&#xff0c;通过可学习的潜变量优化将视觉提示注入到多模态大型语言模型&#xff08;MLLMs&#xff09;中。 在…