位运算算法系列|概念讲解|例题讲解

大家好,我是LvZi,今天带来位运算算法系列|概念讲解|例题讲解
在这里插入图片描述

一,位运算基本概念

1.基础位运算

  • <<:左移操作,相当于 *2
  • >>:右移操作,相当于 /2
  • ~:按位取反
  • &:按位与操作,有0则0
  • |:按位或操作,有1则1
  • ^:按位异或操作,相同为0,相异为1/无进位相加

注:对于^操作,无进位相加值得是在相加的过程中不产生进位操作(出现进位也不相/2加)

  • 需要注意的是尽管通过>>1或者<<1来替代/2和*2,会使得程序的运行速度加快,但是由于优先级的问题,会经常出错,其实也不需要考虑优先级的高低,按照自己的逻辑运算先后添加()即可

如何理解^运算的本质是无进位加法呢

  1. 两个二进制正常相加的结果和十进制相加的结果相同
  2. 二进制相加只可能是三种情况0+0,1+0,1+1
  3. 对于前两种情况:0+0 = 0^0 = 0,0+1 = 0^1 = 1,异或的结果和正常相加的结果相同
  4. 对于最后一种情况,虽然1+1 = 1^1 = 0,但是对于正常的二进制相加,会存在进位,对于^操作,不存在进位,也不会向前进位
  5. 综上,^被称为无进位加法

2.位图

给一个数n,判断 第x位数字是0/1

核心公式:n &= (1 << x),设最后的结果为ret

  • ret == 0 则x == 0
  • ret == 1 则 x == 1

注意:对于一个32位的数字,最右边的下标我们设为0,这样方便进行移位操作

在这里插入图片描述

给一个数n,将第x位修改为1,其余位保持不变

核心公式:n |= (1 << x)

可以这么想,既然要出现1,想想哪个位运算操作容易出现1?当然是|操作,因为按位或是有1则1,特别容易出现1,所以使用|操作,将第x位设置为1,就让1 << x
在这里插入图片描述

给一个数n,将第x位修改为0

核心公式:n &= ~(1 << x)

和上述想法一致,&操作出现0的概率更大,所以使用&操作,让第x位和0 &操作,一定能出现0,其余位都设置为1
在这里插入图片描述

让二进制位不发生改变的算法:&1或者|0

位图

对于一个整数来说,其本质上是一个32位的二进制数,位图思想就是利用这32个0,1数字来存储信息的一种方式

给定一个数n,提取最右侧的1(Lowbit)

核心公式:n & (-n)

n -> (-n),分为两步:

  1. 按位取反 ~
  2. 加一

-n的操作实际上是把n中最右侧的1的左边区域全部取反,右侧保持不变(全是0),1仍是1

所以让(-n)和n进行&,左侧全部变为0右侧也全是0
在这里插入图片描述

给定一个数n,将最右侧的1变为0

核心公式:n & (n - 1)
最右侧的1的右边全为0,如果进行 - 1操作,一定会向前要位,直到要位到最右侧的1,最右侧的1变为0,左侧区域不变,右侧区域全部变为1,这就是 n - 1的最终结果

再进行&操作,左侧区域由于相同,最终的结果不变,右侧区域不同,变为0
在这里插入图片描述

3.异或运算规律

a ^ 0 = a
a ^ a = 0
a ^ b ^ c = a ^ (b ^ c)

第三个运算规律非常好用!

二.例题讲解

01.只出现一次的数字
链接:https://leetcode.cn/problems/single-number/description/
分析

  • 经典的位运算问题,使用^操作的运算性质求单身狗问题
  • a^a = 0; a ^ 0 = a
  • 如果数字出现次数为两次,那么^的结果一定是0,最后只会保存只出现一次的那个数字
class Solution {
    public int singleNumber(int[] nums) {
        int x = 0;
        for(int n : nums)
            x ^= n;
        return x;
    }
}

02.只出现一次的数字(III)
链接:https://leetcode.cn/problems/single-number-iii/description/
分析

  • 本题有两个数字出现的次数均为1,将数组中的每一个元素遍历完毕的结果是这两个数字的异或结果,即a^b,想办法将这两个数字分离,找这两个数字的不同点
  • 找ret的lowbit,a和b两个数字在此位置一定是一个为0,一个为1,根据这个性质可以将整个数组分为两类
  • 如何分离?如果nums在lowbit位置为0,则^lowbit的结果一定是0;

在这里插入图片描述
代码:

    public int[] singleNumber(int[] nums) {
        // 1.获得异或结果
        int ret = 0;
        for(int x : nums) {
            ret ^= x;
        }

        // 2.得到最右边的1
        int bitmask = ret & (-ret);

        // 3.分组
        int a = 0, b= 0;
        for(int x : nums) {
            if((x & bitmask) != 0) a ^= x;
            else b ^= x;
        }

        return new int[]{a,b};
    }

03.位1的个数(经典)
链接:https://leetcode.cn/problems/number-of-1-bits/
分析
本题可以看做一个母题了,核心是利用 & 操作统计一个整数的二进制中有多少1

  • n &= (n-1)这个运算是将n的最右侧的1变为0,每运算一次,n的二进制中的1的个数就少1
  • 最终n会变为0,经过几次 n &= (n-1)运算,就有多少个1
    public int hammingWeight(int n) {
        // 本题是经典的统计一个整数的二进制中1的个数问题
        int cnt = 0;
        while(n != 0) {
            n &= (n-1);// 这个操作每次都把n的最右侧的1变为0  一共变了几次就有多少个1
            cnt++;
        }
        return cnt;
    }

相似题:
比特位计数:https://leetcode.cn/problems/counting-bits/

说明:

  • 对于java来说,在Integer包装类内部内置了bitCount方法,专门统计一个整数n的二进制位中1的数目

04.汉明距离(重点)
链接:https://leetcode.cn/problems/hamming-distance/description/
在这里插入图片描述
分析

  • 本题其实是上一题的拓展,在上题中是统计二进制中1的个数,此题也可以进行转化

  • 如果两个数字对应的二进制位不同,只可能是一个为0,一个为1,异或结果一定是1,相同位置异或的结果就是0(设得到的结果为ret)

  • 则ret的二进制中1的数目就是两个数字二进制位不同的位置的数目,这样就转化为求一个整数对应二进制位中1的个数的问题

  • ^操作相同为0,相异为1

代码:

        // 先异或--结果中的1的个数就是x,y中不同的二进制位的个数
        // 转化为判断n中有多少个1
        int n = x ^ y, ret = 0;
        while(n != 0) {
            n &= (n - 1);
            ret ++;
        }
        return ret;

补充:Java中内置了一个求二进制中1的个数的函数

        // Java中内置的统计1的个数的函数
        return Integer.bitCount(x ^ y);

相似题:
汉明距离总和:https://leetcode.cn/problems/total-hamming-distance/


05.判定字符是否唯一(面试题)
链接:https://leetcode.cn/problems/is-unique-lcci/
分析

  • 此题的常规做法很简单,;利用HashMap或/Set来处理重复情况

  • 进阶是不使用任何的数据结构,但是我们需要使用集合来保存字符,在不使用数组的情况下,有一个特别巧妙的方法位图

  • 对于一个整数来说,其本质上是一个32位的二进制数,可以看做是一个长度为32整形数组,数组中只能存储0或1

  • 我们可以使用0和1来作为一种标识,在本题中每遍历到一个字符,就将对应的下标设置为1,代表该字符已经出现,在添加之前先判断该位置是否为1,如果为1,就代表重复出现
    在这里插入图片描述

    public boolean isUnique(String astr) {
        // 位运算解决
        // 使用位图的思想  因为一共只有26中情况(全为小写)
        // 通过这种标记的方式避免使用额外的数据结构
        // 使用一个32位的二进制数
        // 如果字符出现 就将对应的位置标记为1
        int x = 0;
        for(int i = 0; i < astr.length(); i++) {
            char ch = astr.charAt(i);
            int charIndex = ch - 'a';

            // 判断
            if((x & (1 << charIndex)) != 0) return false;
            x |= (1 << charIndex);
        }

        return true;
    }

方法二

  • 还有一种经典的做法使用^1更改奇偶性
  • 尤其是对于二进制压缩问题,数字要么是0,要么是1,假设0代表出现的次数为偶数,1代表出现的次数为奇数
  • 如果原先是0,^1之后成为1,偶数->奇数
  • 如果原先是1,^1之后成为0,奇数->偶数
  • 如果某个字符出现的次数为偶数次,^1的结果一定是0,根据这个条件判断

代码:

class Solution {
    public boolean isUnique(String astr) {
        int x = 0;// 位图数字
        for(char ch : astr.toCharArray()) {
            int charIndex = ch - 'a';// 找到字符对应的位置(下标从0开始)
            x ^= (1 << charIndex);// 异或操作  0->1  1->0
            if((x & (1 << charIndex)) == 0) return false;// 如果有0  证明出现次数为偶数次
        }
        return true;
    }
}

06.消失的两个数字
链接:https://leetcode.cn/problems/missing-two-lcci/description/
分析

  • 本题是只出现一次的数字III和丢失的数字的综合应用

在这里插入图片描述

代码:

class Solution {
    public int[] missingTwo(int[] nums) {
        int n = nums.length;
        int tmp = 0;
        for(int i = 1; i <= n+2; i++) tmp ^= i;

        for(int i = 0; i < n; i++) tmp ^= nums[i];

        // 2.得到最右边的1
        int bitmask = tmp & (-tmp);

        // 3.分组
        int a = 0, b= 0;
        for(int x : nums) {
            if((x & bitmask) != 0) a ^= x;
            else b ^= x;
        }

        for(int i = 1; i <= n + 2; i++) {
            if((i & bitmask) != 0) a ^= i;
            else b ^= i;
        }

        return new int[]{a,b};
    }
}

07.两整数之和
链接:https://leetcode.cn/problems/sum-of-two-integers/
分析

  • 题目要求不能使用+,-等运算符
  • 最经典的做法就是利用^运算的本质是无进位相加的特点
  • ^运算是无进位加法,那么如何产生进位,使其变成正确的结果呢?要进位的地方是两个数字最右边全是1的两个比特位的最近的左边一位,这个位置的计算结果相较于正确结果少了1,需要在这个位置加上1(注意加上之后可能有存在进位,应该重复执行上述操作,直到进位为0)

代码:
循环解法

class Solution {
    public int getSum(int a, int b) {
        while (b != 0) {
            int x = a ^ b;// 无进位加法
            int carry = ((a & b) << 1);// 计算进位
            a = x;
            b = carry;
        }
        return a;
    }
}

递归解法

class Solution {
    public int getSum(int a, int b) {
        if(b == 0) return a;
        return getSum(a ^ b, ((a & b) << 1));
    }
}

09.2的幂
链接:https://leetcode.cn/problems/power-of-two/submissions/542332234/
分析

  • 2的幂的二进制位中,只有一个1

代码
方法一:统计二进制位中1的个数

class Solution {
    public boolean isPowerOfTwo(int n) {
        if(n < 0) return false;
        return Integer.bitCount(n) == 1;
    }
}

方法二:利用n&(n - 1)

  • n&(n-1):将n中最右侧的1更改为0
  • 由于2的幂只有一个1,更改为0之后一定为0
class Solution {
    public boolean isPowerOfTwo(int n) {
        if(n <= 0) return false;
        return ((n & (n - 1)) == 0);
    }
}

10.位运算其他技巧总结
1.不用临时变量交换两个数

int a = 0, b = 0;
a ^= b; // a = a ^ b
b ^= a; // b = b ^ a ^ b = a
a ^= b; // a = a ^ b ^ a = b

2.求绝对值
使用位运算来求整数的绝对值的表达式是 (x ^ (x >> 31)) - (x >> 31)。这是如何工作的:

  1. x >> 31

    • 这一步将 x 右移31位,如果 x 是一个32位的整数(考虑到符号位),它将填充符号位。对于正数(或0),结果是0;对于负数,结果是-1。
    • 例如,对于 x = -5
      • 二进制表示: 11111111 11111111 11111111 11111011
      • 右移31位: 11111111 11111111 11111111 11111111(即-1)
      • 对于 x = 5
      • 二进制表示: 00000000 00000000 00000000 00000101
      • 右移31位: 00000000 00000000 00000000 00000000(即0)
  2. x ^ (x >> 31)

    • 这一步利用异或运算(XOR)将 x 进行条件取反。如果 x 是负数,它将 x 的每个位进行翻转。如果 x 是正数,这一步不会改变 x
    • 例如,对于 x = -5
      • -5 右移31位结果是-1(即所有位都是1)
      • x ^ -1 等于 ~x(即对 x 取反): 00000000 00000000 00000000 00000100(即4)
      • 对于 x = 5
      • 5 右移31位结果是0
      • x ^ 0 结果仍然是 5
  3. (x ^ (x >> 31)) - (x >> 31)

    • 如果 x 是负数,x 取反之后再减去-1,相当于加1,这样就得到了 x 的绝对值。
    • 例如,对于 x = -5
      • (x ^ -1) 结果是4
      • 再减去-1,结果是5
    • 对于 x = 5
      • (x ^ 0) 结果是5
      • 再减去0,结果仍然是5

代码实现如下:

public int absolute_value(x) {
    return (x ^ (x >> 31)) - (x >> 31);
}    

3.判断两个数符号是否相同
使用位运算判断两个数的符号是否相同可以通过 (a ^ b) >= 0 来实现。以下是原理解释:

  1. a ^ b

    • 异或运算 a ^ b 会比较 ab 的每一个对应的二进制位,如果相同则结果为0,不同则结果为1。
    • 对于符号位(最高位),如果 ab 的符号相同(即符号位相同),则 a ^ b 的符号位将是0;如果符号不同,符号位将是1。
    • 因此,如果 ab 符号相同,结果是非负数(符号位为0);如果符号不同,结果是负数(符号位为1)。
  2. (a ^ b) >= 0

    • 通过检查 a ^ b 是否为非负数,可以判断 ab 的符号是否相同。如果 a ^ b 的结果大于等于0,说明符号相同;否则符号不同。

代码实现如下:

public boolean have_same_sign(a, b){
    return (a ^ b) >= 0;
}

总结:

  1. 对于位运算这个算法,预期叫做一种思想,不如当做一种技巧,是一个加快运算速度的技巧
  2. 记住常见的集中位运算的性质和经典题目就可(求二进制中1的个数.利用^操作更改奇偶性,利用&1操作判断元素的奇偶性…)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第三届仿真模拟、电子信息科学与技术国际学术会议(SMEI 2024,8月02-04)

随着仿真模拟技术的成熟和进步&#xff0c;仿真模拟技术越来越广泛地应用于工业工程、管理科学、社会经济、交通运输、生态环境、军事装备等各个科学领域&#xff0c;并深刻影响着信息技术和信息产业的发展。围绕仿真模拟、电子信息科学与技术等方面内容&#xff0c;为更好地促…

电脑定时重启怎么设置?用这个智能管理电脑定时任务的好帮手!

电脑定时重启怎么设置&#xff1f;用这个智能管理电脑定时任务的好帮手&#xff01;电脑定时重启&#xff0c;这个设置其实很简单&#xff0c;但是很多人都不知道用电脑怎么设置&#xff0c;而且操作也很麻烦&#xff0c;并不好管理&#xff0c;这个时候我们需要一个非常智能的…

每个 Node.js 开发人员都应该知道的13个库(下)

7. Sequelize Mongoose是一个Node。基于js的MongoDB对象建模工具&#xff0c;通常被称为对象数据建模&#xff08;ODM&#xff09;库&#xff0c;它提供了诸如钩子、模型验证、连接和查询等功能。 Mongoose为应用程序数据提供了一个基于模式的解决方案&#xff0c;它在应用程…

【数据同步】什么是ETL增量抽取?

目录 一、什么是ETL增量抽取 二、企业如何应用ETL增量抽取 三、如何进行ETL增量抽取 1.基于时间戳的增量抽取 2.基于主键的增量抽取 在当今信息化时代&#xff0c;数据的快速增长和多样化使得企业面临着巨大的数据管理挑战。为了高效地处理和利用数据&#xff0c;ETL&#xff0…

JAVA进阶学习09

文章目录 一、双列集合Map1.1 双列集合介绍1.2 双列集合Map常见API1.3 Map集合遍历方式1.3.1 通过集合的全部键来遍历集合1.3.2 Map集合遍历方式21.3.3 Map集合遍历方式3 二、Map集合的实现类2.1 HashMap类2.2 LinkedHashMap2.3 TreeMap 三、可变参数四、Collections类五、集合…

一文梳理有效提升RAG效果的方法

来源&#xff1a;一文梳理有效提升RAG效果的方法 在大模型实际落地的时候&#xff0c;存在一些问题&#xff0c;主要集中在以下方面&#xff1a; 缺少垂直领域知识&#xff1a;虽然大模型压缩了大量的人类知识&#xff0c;但在垂直场景上明显存在短板&#xff0c;需要专业化的…

查询DBA_TEMP_FILES报错,删除临时表空间报错ORA-60100

SYMPTOMS 查询DBA_TEMP_FILES报错如下图 ORA-01157: cannotidentify/ock data fle 201 -see DBWR trace fle ORA-01110: data fle 20 1: D:APPADMINISTRATORIORADATA MARTIDATAFILE 01157,00000-"cannotidentify/ock data fle %s -see DBWR trace fle"*Cause: The b…

收银系统开源源码-千呼新零售2.0【打折促销】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

Windows应急响应靶机 - Web3

一、靶机介绍 应急响应靶机训练-Web3 前景需要&#xff1a;小苕在省护值守中&#xff0c;在灵机一动情况下把设备停掉了&#xff0c;甲方问&#xff1a;为什么要停设备&#xff1f;小苕说&#xff1a;我第六感告诉我&#xff0c;这机器可能被黑了。 这是他的服务器&#xff…

计算机网络模型(OSI架构、TCP/IP架构)

OSI开放式系统互联 为什么会有通用的网络通信模型&#xff08;OSI、TCP/IP&#xff09;一、OSI&#xff08;1&#xff09;OSI 是什么&#xff08;2&#xff09;OSI 七层第七层、应用层第六层、表示层第五层、会话层第四层、传输层第三层、网络层第二层、数据链路层第一层、物理…

递归(一)——用“单步调试法”来理解递归调用过程

在算法的学习过程中&#xff0c;“递归”算法似乎显得很神秘&#xff0c;时常让学习者一头雾水&#xff0c;感觉莫名其妙&#xff0c;可是掌握递归又是一个绕不过去的坎&#xff0c;因为很多更高级的数据结构和算法思想就是以递归为基础的&#xff0c;比如数据结构中的树和图&a…

工商业储能柜用的Acrel-2000ES储能能量管理系统-安科瑞 蒋静

概述 Acrel-2000ES储能能量管理系统&#xff0c;专门针对工商业储能柜、储能集装箱研发的一款储能EMS&#xff0c;具有完善的储能监控与管理功能,涵盖了储能系统设备(PCS、BMS、电表、消防、空调等)的详细信息&#xff0c;实现了数据采集、数据处理、数据存储、数据查询与分析…

数据结构9——排序

一、冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;&#xff0c;顾名思义&#xff0c;就是指越小的元素会经由交换慢慢“浮”到数列的顶端。 算法原理 从左到右&#xff0c;依次比较相邻的元素大小&#xff0c;更大的元素交换到右边&#xff1b;从第一组相邻元素比较…

Talk|北京大学PKU-DAIR余昭辰:从多模态理解到生成 - 从LLM到Diffusion Model

本期为TechBeat人工智能社区第603期线上Talk。 北京时间6月26日(周三)20:00&#xff0c;北京大学PKU-DAIR实习生—余昭辰的Talk已经准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “从多模态理解到生成 - 从LLM到Diffusion Model”&#xff0c;在本次Talk…

.Net WebApi启动 Swagger异常报错: Failed to load API definition

问题描述&#xff1a; 基于.Net6.0的WebApi 启动Swagger报错&#xff1a;Failed to load API definition。即无法加载API定义。 解决方法&#xff1a; 分析程序输出日志&#xff1a; 错误信息&#xff1a; ERROR Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMid…

无线领夹麦克风品牌排名,揭秘哪种领夹麦性价比高!

在直播电商和Vlog的热潮推动下&#xff0c;自媒体内容创作迎来了前所未有的繁荣。麦克风行业也因应这一趋势&#xff0c;迎来了快速的增长期。特别是无线领夹麦克风&#xff0c;以其便携性和高效的录音能力&#xff0c;迅速成为视频制作者的新宠。它不仅在直播带货和短视频制作…

[JS]DOM事件

事件监听 让程序检测是否有事件产生, 一旦事件触发, 就调用函数做出响应 事件三要素: 事件源(谁的事件) 事件类型(如何触发) 事件处理程序(做什么) function fn() {} // 绑定事件 btn.addEventListener(click, fnction() { })// 绑定事件 btn.addEventListener(click, fn)//…

openlayer 图层点击事件 鼠标单击

背景&#xff1a; 接上一篇博客&#xff0c;如何渲染图层&#xff0c;渲染不同颜色的图层&#xff1f; 一个图层创建好了&#xff0c;接下来我们要做的是&#xff0c;如何通过鼠标点击打开点击对象的详情弹框&#xff1f;鼠标点击的是layer图层里的featrue要素&#xff0c;这…

数字AI化银行数字化转型实战手册银行数字化转型大客户营销销售讲师培训师唐兴通谈存量客户理财金融科技与场景化

推动银行数字化转型的五个关键因素 推动银行数字化转型的五个关键因素&#xff1a; 客户体验。为客户提供便利和个性化是数字化转型的关键因素。银行应开发和实施创新的数字渠道&#xff0c;例如移动应用程序、网上银行、聊天机器人等&#xff0c;以方便获取金融服务并提高客户…

使用微信开发者工具创建运行项目全流程

小程序基础知识 1. 认识什么是小程序 什么是微信小程序 微信小程序是一种运行在微信内部的 轻量级 应用程序。 在使用小程序时 不需要下载安装&#xff0c;用户 扫一扫 或 搜一下 即可打开应用。它也体现了 “用完即走” 的理念&#xff0c;用户不用关心安装太多应用的问题…