常见的位操作及其应用

概述

与、或、异或、取反或者移位运算这几种基本的位操作想必诸位读者并不陌生,如果我们能在某些合适场景下使用位运算,有些时候可以大大提高算法的效率。但由于本身位运算太过灵活,甚至某些技巧比较苦涩难懂,因而,本篇文章主要介绍几种常见的或者有趣的位操作,并且给出一些用到这些技巧的算法题目,便于读者练习。

有趣的操作

1. 大小写字母转换

  1. 利用 或操作和空格将英文字母转成小写
('a' | ' ') = 'a';
('A' | ' ') = 'a';
  1. 利用与运算 &和下划线将英文字符转换成大写
('b' & '_') = 'B';
('B' & '_') = 'B';
  1. 利用异或运算 ^和空格进行英文字符大小写互换
('d' ^ ' ') = 'D';
('D' ^ ' ') = 'd'

常用指数: 🔯🔯

容易指数:🔯🔯🔯

PS:上述技巧能够产生奇特效果的原因在于字符类型的数据都是通过ASCII进行编码的,字符本身其实就是数字,而刚好这些字符对应的数字通过位运算符就可以得到正确结果,此处就不展开来说了。

2. 判断两个数是否异号

int x = -1, y = 2;
boolean f = ((x ^ y) < 0); // true 两个int类型数据进行异或运算小于零证明异号

int x = 1, y = 2;
boolean f = ((x ^ y) < 0); // false 两个int类型数据进行疑惑运算大于零证明同好

常用指数:🔯🔯🔯

困难指数:🔯

PS:这个操作在我们判断两个数异号的时候 非常有用,一方面运算效率较高,另一方面可以减少if else 分支的使用。其背后的原理主要是一个正数补码的符号位和一个负数补码的符号位肯定想法,经过疑惑运算后,最后符号位结果肯定是 1(代表负数)。

3. 移除最后一位”1″

byte n = 10;
// n的二进制表示为: 0 0 0 0 1 0 1 0
//       异或运算  ^
//n-1的二进制表示为:0 0 0 0 1 0 0 1
n & (n-1); //结果为:0 0 0 0 1 0 0 0

常见的位操作及其应用

根据上边的注释以及示意图,整个操作过程应该不难理解

简单来说 n & (n -1 ) 主要作用:就是 消除数字n的二进制表示中的最后一个1.

常用指数:🔯🔯🔯🔯🔯

困难指数:🔯🔯

PS:这个操作特别常用,在好多leetcode题目中都有涉及,比如用来判断一个数的二进制数中1的个数。

4. 获取最后一个1

byte n = 10;
//结果为:0 0 0 0 0 0 1 0
(n & (n-1)) ^ n;

常用指数:🔯🔯🔯🔯

困难指数:🔯🔯🔯

PS:该操作刚好和操作3相反, 主要是为了获取数字n的二进制表示中的最后一个1.该操作通常会用在位标记的时候使用(可参考汉明距离)。

5. 异或运算的简单性质

a=0^a=a^0

0=a^a

常用指数:🔯🔯🔯🔯

困难指数:🔯

PS:这个性质在我们判断两个数是否相同的时候 非常常用。

应用

前边总结了那么多常用的位操作,下边来做几道题消化吸收一下刚刚所学的知识。

1. 只出现一次的数字

常见的位操作及其应用

这道题比较简单,说实话即使不用位运算我们也可以有好多种方法求解,比如可以用一个Map来对元素进行boolean标记来求解。但如果我们此处能想到用 异或运算的性质,那这道题目我们便可以简单优雅的解出来。

思路:用一个初值为0的变量不断和数组中的元素进行异或运算,最终得到的变量值便是最终结果。

原因:因为0和任何一个元素进行异或运算都是0,而任何两个相同元素进行异或之后的结果都是0,而题目中只有一个数是单独存在的,其他数都是2个,因而不断进行异或运算最后的结果必然是那个独特的数值,也就是最终的结果。

代码如下:

public int singleNumber(int[] nums) {
        int result = 0;
        for (int i =0;i

2. 汉明距离

常见的位操作及其应用

这个问题,我们常规思路可能是通过一次 for&#x5FAA;&#x73AF;并且在循环过程中判断不同位置的二进制位是否相同,同时做计数。

但同样的我们通过位运算可以比较快速的解决该问题

思路:通过一次异或运算,获取一个不同位置二进制数构成的一个整数,然后计算该整数中1的个数即为最终结果。在计算1个数的时候一个小技巧,可以通过n & (n-1)不断做移位运算来计算。

实现代码如下:

 public int hammingDistance(int x, int y) {
    x = x ^ y;
    int count = 0;
    while (x != 0) {
      x = x & (x - 1);
      count++;
    }
    return count;
  }

1. 只出现一次的数字 III

常见的位操作及其应用

这个问题跟只出现一次的数字很像,主要差别可能在这个唯一的数是 两个,而不是一个。同样的这个问题有很多常规的结果,比如街主要map来统计和标注每个数字出现的次数。但我们使用位运算的时候我们会发现其速度是极快的。

思路:

前面分析了此题目和原来题目的最大区别在于只出现一次的数字 不再是唯一的了,而变成了两个。因而我们考虑能否对着集合元素按照 某种标准做一个 分类,经过分类之后,每一个类别都分别包含一个特殊的元素以及若干相同的元素。此时该问题就转换成了问题1,那个简单的问题。

其实分类标准应该不难找,由于这两个元素是不相等的,因此这两个元素的某个二进制位必然是不相等。因此我们可能考虑根据 该二进制位为0或者1将数组分成A组合B组。而这个数组中其它数字要么就属于A组,要么就属于B组。再对A组和B组分别执行”异或”解法就可以得到A,B了。而要判断A,B在哪一位上不相同,只要根据A异或B的结果就可以知道了,这个结果在二进制上为1的位就说明A,B在这一位上是不相同的。

比如:

int a[] = {1, 1, 3, 5, 2, 2}

整个数组异或的结果为3^5,即 0x0011 ^ 0x0101 = 0x0110

0x0110,第1位(由低向高,从0开始)就是1。因此整个数组根据第1位是0还是1分成两组。

a[0] =1  0x0001  第一组

a[1] =1  0x0001  第一组

a[2] =3  0x0011  第二组

a[3] =5  0x0101  第一组

a[4] =2  0x0010  第二组

a[5] =2  0x0010  第二组

第一组有{1,1,5},第二组有{3,2,2},然后对这二组分别执行”异或”解法就可以得到5和3了。\

实现代码如下:

public int[] singleNumber(int[] nums) {
    int xorVal = 0;
    for (int num : nums) {
        xorVal ^= num;
    }
    // 获取最后一个 1
    xorVal = (xorVal & (xorVal - 1)) ^ xorVal;

    int res[] = new int[2];
    //根据和xorVal的与运算结果不同进行分类
    for (int num : nums) {
        if ((num & xorVal) == 0) {
            res[0] ^= num;
        } else {
            res[1] ^= num;
        }
    }
    return res;
}

总结

以上便是一些常用的位操作,以及对应的一些简单应用。其实关于微操作的技巧很多,有很多也非常有趣,其中有一个叫做 BitTwiddling Hacks 的外国网站收集了几乎所有位操作的黑科技玩法,感兴趣的话,可以看一看哈!!

参考

  1. https://leetcode-cn.com/problems/single-number-iii/solution/java-yi-huo-100-yao-shi-kan-bu-dong-wo-jiu-qu-chu-/
  2. https://greyireland.gitbook.io/algorithm-pattern/shu-ju-jie-gou-pian/binary_op#chang-jian-ti-mu

Original: https://www.cnblogs.com/goWithHappy/p/bit-operation.html
Author: vcjmhg
Title: 常见的位操作及其应用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599383/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java 多线程共享模型之管程(下)

    共享模型之管程 wait、notify wait、notify 原理 Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态 B…

    数据库 2023年6月16日
    0116
  • 分布式任务调度平台XXL-JOB安装

    安装xxl-job-admin 1.拉取镜像 #拉取镜像 docker pull xuxueli/xxl-job-admin:2.3.0 #新建挂载目录 mkdir /usr/lo…

    数据库 2023年6月11日
    084
  • 几款ZooKeeper可视化工具,最后一个美炸了~

    首发于公众号:BiggerBoy欢迎关注,查看更多技术文章 ZooKeeper是我们工作中常用一个开源的分布式协调服务,提供分布式数据一致性解决方案,分布式应用程序可以实现数据发布…

    数据库 2023年6月11日
    099
  • 国内访问github很慢或者访问不了,解决办法

    国内因为某些问题,会屏蔽国外一些网站。有时GitHub访问的时候会出现错误,无法访问。解决办法 进入https://fastly.net.ipaddress.com/(全球最好的I…

    数据库 2023年6月6日
    0127
  • MySQL数据库CRUD

    INSERT语句 INSERT INTO 表名 (column1,column2,column3,…)VALUES (value1,value2,value3,&#82…

    数据库 2023年6月16日
    088
  • c++ map查找键值

    map用法 查找键是否存在 1、count函数 count函数用于统计key值在map中出现的次数,map的key不允许重复,因此如果key存在返回1,不存在返回0 if (mp….

    数据库 2023年6月6日
    0257
  • 多态:向上转型和向下转型

    1)本质:父类的引用指向了子类的对象 2)语法:父类类型 引用名 = new 子类类型(); 3)特点:编译类型看左边,运行类型看右边。 可以调用父类中的所有成员(需遵守访问权限)…

    数据库 2023年6月11日
    091
  • MySQL启动过程详解三:Innodb存储引擎的启动

    Innodb启动过程如下: 初始化innobase_hton,它是一个handlerton类型的指针,以便在server层能够调用存储引擎的接口。 Innodb相关参数的检车和初始…

    数据库 2023年6月9日
    096
  • MySQL实战45讲 17

    17 | 如何正确地显示随机消息? 场景:从一个单词表中随机选出三个单词。 表的建表语句和初始数据的命令如下,在这个表里面插入了 10000 行记录: CREATE TABLE w…

    数据库 2023年6月14日
    067
  • Javaweb10-javaweb其他知识点

    1、详解DefaultServlet与JspServlet 当服务端收到关于 Servlet的请求之后交由 自定义Servlet处理。 当服务端收到关于 静态资源的请求时交由 De…

    数据库 2023年6月16日
    078
  • MySQL中读页缓冲区buffer pool

    Buffer pool 我们都知道我们读取页面是需要将其从磁盘中读到内存中,然后等待CPU对数据进行处理。我们直到从磁盘中读取数据到内存的过程是十分慢的,所以我们读取的页面需要将其…

    数据库 2023年5月24日
    0114
  • openpyxl使用总结

    设置表头单元格的颜色 fill = PatternFill("solid", fgColor=’FF000000′) font = Font(color=’00…

    数据库 2023年6月9日
    085
  • win7连接远程桌面提示身份验证错误函数不受支持

    win7连接远程桌面提示身份验证错误。要求的函数不受支持怎么办,下面的方法介绍了如何解决这个问题。 工具/原料 电脑 win7系统 方法/步骤 win+R 打开运行,输入&#822…

    数据库 2023年6月9日
    084
  • 2021年想做的最后挣扎

    一年的时间转眼间就过完,感觉没变,又感觉跟一年前的今天变化还是蛮多的,树立个小目标争取年前完成把 读书一本书看一篇文章: 《百年孤独》:我总感觉虽然是只单身狗是孤单的,理解不了孤独…

    数据库 2023年6月6日
    076
  • 解决PHP undefined function mcrypt_encrypt()的报错问题

    今天迁移服务器代码遇到了一个未定义的错误 查找了相关资料后,发现是缺少php_mcrypt扩展 于是去下载扩展: https://windows.php.net/downloads…

    数据库 2023年6月14日
    091
  • 操作系统(学习笔记)

    操作系统(学习笔记) PCB=process control block=进程控制块,用于存储进程相关信息,以便进程切换; GDT=global descriptor table=…

    数据库 2023年6月14日
    082
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球