前言 位运算隐藏在编程语言的建议技巧角落中,其神秘而又强大,收藏暗藏内力,面试有些人光听位运算的运算大名的心中忐忑,还有些人更是奇淫一看到位运算就远远离去,我之前也是建议技巧。但狡猾的收藏面试官往往喜欢搞偷袭,抓住我们的面试弱点搞我们,为了防患于未然,运算特记此篇! 本篇的奇淫内容为位运算的介绍和一些比较经典的位运算问题进行介绍分析,当然,建议技巧位运算这么牛,收藏后面肯定还是面试要归纳总结的。 认识位运算 什么是运算位运算? 位运算就是直接操作二进制数,那么有哪些种类的位运算呢? 常见的运算符有与(&)、或(|)、异或(^)、取反(~)、左移(<<)、右移(>>是带符号右移 >>>无符号右移动)。下面来细看看每一种位运算的规则。 位运算 & (与) 规则:二进制对应位两两进行逻辑AND运算(只有对应位的值都是 1 时结果才为 1, 否则即为 0)即 0&0=0,0&1=0,1&1=1 例如:2 & -2 位运算 | (或) 规则:二进制对应位两两进行逻辑或运算(对应位中有一 个为1则为1) 即0|0=0,0|1=1,1|1=1 例如:2 | -2 位运算 ^ (异或) 规则:二进制对应位两两进行逻辑XOR (异或) 的运算(当对应位的值不同时为 1, 否则为 0)即0^0=1, 0^1=0, 1^1=1 例如:2 ^ -2 按位取反~ 规则:二进制的0变成1,1变成0。 移位运算符 左移运算<<:左移后右边位补 0 右移运算>>:右移后左边位补原最左位值(可能是0,可能是1) 右移运算>>>:右移后左边位补 0 下面这张图可以很好的帮助你理解负数的移位运算符: 到这里,我想你应该对位运算有了初步的认识,在这里把上面提到的部分案例执行对比一下让你看一下可能会理解的更清晰: 位运算小技巧 在这里有些常用的位运算小技巧。 判断奇偶数 正常判断奇数偶数的时候我们会这样写: 使用位运算可以这么写: 其核心就是判断二进制的最后一位是否为1,如果为1那么结果加上2^0=1一定是个奇数,否则就是个偶数。 交换两个数 对于传统的交换两个数,亿华云计算我们需要使用一个变量来辅助完成操作,可能会是这样: 但是使用位运算可以不需要借助额外空间完成数值交换: 原理已经写在注释里面了,是不是感觉非常diao呢? 二进制枚举 在遇到子集问题的处理时候,我们有时候会借助二进制枚举来遍历各种状态(效率大于dfs回溯)。这种就属于排列组合的问题了,对于每个物品(位置)来说,就是使用和不使用的两个状态,而在二进制中刚好可以用1和0来表示。而在实现上,通过枚举数字范围分析每个二进制数字各符号位上的特征进行计算求解操作即可。 二进制枚举的代码实现为: 位运算经典问题 有了上面的位运算基础,我们怎么用位运算处理实际问题呢?或者有哪些经典的问题可以用位运算来解决呢。 不用加减乘除做加法 题目描述 分析:这道题咋一听可能没啥思路,简单研究一下位运算还是能独立推出来和理解的。 当然,解决这题前,需要了解上面的四种位运算。还要知道二进制的运算:0+0=0,0+1=1,1+1=0(进位) 对于加法的一个二进制运算。如果不进位那么就是非常容易的。这时候相同位都为0则为0,0和1则为1.满足这种运算的异或(不相同取1,相同取0)和或(有一个1则为1)都能满足. 但事实肯定有进位的运算啊!看到上面操作的不足之后,我们肯定还需要解决进位的问题对于进位的两数相加,这种核心思想为: 实现代码为: 当然,这里也可以科普一下二进制求加法:average = (a&b) + ((a^b)>>1) ; 二进制中1的个数 这是一道经典题,在剑指offer上也有对应题目,其具体题目要求输入一个整数,输出该数二进制表示中1的个数(其中负数用补码表示)。 对于这个问题,不用位运算将它转成二进制字符串直接枚举字符1的个数也可以直接求出来,但是这样做是没有灵魂的并且效率比较差。这里提供两种解决思路 法一:大家知道每个类型的数据它的背后实际都是二进制操作。大家知道int的数据类型的范围是(-2^31,2^31 -1)。并且int有32位。但是并非32位全部用来表示数据。真正用来表示数据大小的也是31位。最高位用来表示正负。 首先要知道: 其次还要知道位运算&与。两个十进制与运算.每一位同1为1。所以我们用2的正数次幂与知道的数分别进行与运算操作。如果结果不为0,那么就说明这位为1.(前面31个都是大于0的最后一个与结果是负数但是如果该位为1那么结果肯定不为0) 具体代码实现为: 法二是运用n&(n-1)。n如果不为0,那么n-1就是二进制第一个为1的变为0,后面全为1.这样的n&(n-1)一次运算就相当于把最后一个1变成0.这样一直到运算的数为0停止计算次数就好了,如下图共进行三次运算那么n的二进制中就有三个1。 实现代码为: 只出现一次的(一个)数字① 问题描述: 分析: 这是一道简单的面试题,面试官常问怎么样用不太复杂的方法找出数组中仅出现一次的数字(其他均出现两次),暴力枚举或者使用其他的存储结构都不够优化,而这个问题最高效的答案就是使用位运算。首先你要注意两点: 具体的操作就是用0开始和数组中每个数进行异或,得到的值和下个数进行异或,最终获得的值就是出现一次(奇数次)的值。 只出现一次的(一个)数字② 问题描述: 分析: 这题和上一题的思路略有不同,这题其他数字出现了3次,那么我们如果直接使用位运算异或操作的话无法直接找到结果,就需要巧妙的运用二进制的其他特性:判断整除求余操作。即判断所有数字二进制1的总个数和0的总个数一定有一个不是三的整数倍,如果0不是三的整数倍那么就说明结果的该位二进制数字为0,同理否则为1. 在具体的操作实现上,问题中给出数组中的数据在int范围之内,那么我们就可以在实现上可以对int的32个位每个位进行依次判断该位1的个数求余3后是否为1,如果为1说明结果该位二进制为1可以将结果加上去。最终得到的值即为答案。 具体代码为: 只出现一次的(两个)数字③ 题目描述: 思路: 上面的问题处理和理解起来可能比较容易,但是这个问题可能稍微复杂一点,但是这题可以通过特殊的手段转化为上面只出现一次的一个数字问题来解决,当然核心的位运算也是异或^。 具体思路就是想办法将数组逻辑上一分为二!先异或一遍到最后得到一个数,得到的肯定是a^b(假设两个数值分别为a和b)的值。在看异或^的属性:不同为1,相同为0. 也就是说最终这个结果的二进制为1的位置上a和b是不相同的。而我们可以找到这个第一个不同的位,然后将数组中的数分成两份,该位为0的进行异或求解得到其中一个结果a,该位为1的进行异或求解得到另一个结果b。 具体可以参考下图流程: 实现代码为: 结语 当然,上面的问题可能有更好的解法,也有更多经典位运算问题将在后面归纳总结,希望本篇的位运算介绍能够让你有所收获,对位运算能有更深一点的认识。对于很多问题例如博弈问题等二进制位运算能够很巧妙的解决问题,日后也会分享相关内容,敬请期待!class Solution { public int singleNumber(int[] nums) { int value=0; for(int i=0;i<nums.length;i++) { value^=nums[i]; } return value; } }