精华内容
下载资源
问答
  • 异或运算怎么算_位运算

    千次阅读 2020-12-13 09:40:18
    觉得你还是有点逼格的,巧用位运算,不仅会提升性能,还会让代码的可读性更好,达到四两拨千斤的效果,今天我们就来学学位运算在解题中的一些技巧,最后会用位运算来看看怎么解八皇后这道大 B...
    有道云笔记地址note.youdao.com

    位运算在生产或算法解题中并不常见,不过如果你用得好,可以达到事半功倍的效果,而且位运算用得好,也可以极大地提升性能,如果在生产或面试中能看到使用位运算来解题,会让人眼前一亮,觉得你还是有点逼格的,巧用位运算,不仅会提升性能,还会让代码的可读性更好,达到四两拨千斤的效果,今天我们就来学学位运算在解题中的一些技巧,最后会用位运算来看看怎么解八皇后这道大 Boss 题,相信你看完肯定会有收获!

    本文将会从以下几个方面来讲解位运算

    • 什么是位运算,位运算常见操作
    • 位运算使用技巧简介
    • 巧用位运算解算法题

    什么是位运算,位运算常见操作

    在现代计算机中所有的数据在内存中都是以二进制存在的,位运算就是直接对整数在内存中的二进制位进行操作,由于位运算直接对内存数据进行操作,无需转成十进制,因此使用位运算的处理速度是很快的。

    举个简单的例子, 当我们要计算 6 & 4 的结果,在做位运算的时候首先要把 6,4 转成二进制,然后再做相应的位操作(与)。

    307be76403d078b4da3f4783e6f6cd27.png

    基本的位运算有与、或、异或、取反、左移、右移这6种,介绍如下:

    & 与:只有当两位都是 1 时结果才是 1,否则为 0 。

    0110 & 0100 ----------- 0100

    | 或:两位中只要有 1 位为 1 结果就是 1,两位都为 0 则结果为 0。

    0110 & 0110 ----------- 0110

    ^ 异或:两个位相同则为 0,不同则为 1

    0110 ^ 0100 ----------- 0010

    ~ 取反:0 则变为 1,1 则变为 0

    ~ 0110 ----------- 1001

    << 左移:向左进行移位操作,高位丢弃,低位补 0

    int a = 8; a << 3; 移位前:00000000000000000000000000001000 移位后:00000000000000000000000001000000

    >> 右移:向右进行移位操作,对无符号数,高位补 0,对于有符号数,高位补符号位

    unsigned int a = 8; a >> 3; 移位前:00000000000000000000000000001000 移位后:00000000000000000000000000000001 int a = -8; a >> 3; 移位前:11111111111111111111111111111000 移位后:11111111111111111111111111111111

    位运算使用技巧简介

    接下来我们就由浅入深地来学习一下使用位运算的那些黑科技

    1、 判断整型的奇偶性

    使用位运算操作如下

    if((x & 1) == 0) { // 偶数 } else { // 奇数 }

    这个例子相信大家都见过,只需判断整型的第一位是否是 1 即可,如果是说明是奇数,否则是偶数

    2、 判断第 n 位是否设置为 1

    if (x & (1<<n)) { // 第 n 位设置为 1 }else { // 第 n 位设置为 1 }

    在上例中我们判断第一位是否为 1,所以如果要判断第 n 位是否 1,只要把 1 左移 n 位再作与运算不就完了。

    3、 将第 n 位设置为 1

    y = x | (1 << n)

    思路同第二步,先把 1 移到第 n 位再作或运算,这样第 n 位就肯定为 1。

    4、 将第 n 位设置为 0

    y = x & ~(1<<n)

    先将 1 左移到 第 n 位,再对其取反,此时第 n 位为 0,其他位都为 1,这样与 x 作与运算后,x 的第 n 位肯定为 0。

    5. 将第 n 位的值取反

    y = x ^ (1<<n)

    我们知道异或操作是两个数的每一位相同,结果为 0,否则是 1,所以现在把 1 左移到第 n 位,则如果 x 的第 n 位为 1,两数相同结果 0,如果 x 的第 n 位为 0,两数不相同,则为 1。来看个简单的例子

    01110101 ^ 00100000 -------- 01010101

    如图示,第五位刚好取反

    6、 将最右边的 1 设为 0

    y = x & (x-1)

    如果说上面的 5 点技巧有点无聊,那第 6 条技巧确实很有意思,也是在 leetcode 经常出现的考点,下文中大部分习题都会用到这个知识点,务必要谨记!掌握这个很重要,有啥用呢,比如我要统计 1 的位数有几个,只要写个如下循环即可,不断地将 x 最右边的 1 置为 0,最后当值为 0 时统计就结束了。

    count = 0 while(x) { x = x & (x - 1); count++; }

    先介绍这么多吧,如果大家对其他的位运算技巧感兴趣可以看看文末的参考链接

    巧用位运算解算法题

    接下来我们看看位运算在算法题中的应用。

    1、 高频面试题:老鼠试毒

    有 8 个一模一样的瓶子,其中有 7 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 3 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?

    解题步骤如下:

    1、 把这 8 个瓶子从 0 到 7 进行编号,用二进制表示如下

    000 001 010 011 100 101 110 111

    2、 将 0 到 7 编号中第一位为 1 的所有瓶子(即 1,3,5,7)的水混在一起给老鼠 1 吃,第二位值为 1 的所有瓶子(即2,3,6,7)的水混在一起给老鼠 2 吃, 第三位值为 1 的所有瓶子(4,5,6,7)的水混在一起给老鼠 3 吃,现在假设老鼠 1,3 死了,那么有毒的瓶子编号中第 1,3 位肯定为 1,老鼠 2 没死,则有毒的瓶子编号中第 2 位肯定为 0,得到值 101 ,对应的编号是 5, 也就是第五瓶的水有毒。

    这道题及其相关的变种在面试中出现地比较频繁,比如我现在把 8 瓶水换成 1000 瓶,问你至少需要几只老鼠才能测出有毒的瓶子,有了上述的思路相信应该不难,几只老鼠就相当于几个进制位,显然 2^10 = 1024 > 1000,即 10 只老鼠即可测出来。

    2、 leetcode 232

    给定一个数,判断它是否是可以用 2 的幂次方表示,可以返回 true,不可以返回 false,比如 8 = 2^3, 说明可以用 2 的幂次方表示,返回 true,9 不可以,所以返回 false。

    解题分析:这题常规解法是做个循环不断地乘以 2 ,看下是否等于给定的值,如果等于说明是 2 的幂次方,否则如果不断累乘 2 后大于给定的值,说明不能用 2 的幂次方表示,时间复杂度是所做的累乘的次数,即 2^n >= 给定的值中的 n。

    那是否有更快的解法呢?

    上文的介绍中其实我们已经埋下伏笔了,没错用 x & (x-1),首先我们要发现能用 2 的幂次方表示的数的特点:它的所有位中有且仅有一位为 1,如

    00001 2^0 = 1 00010 2^1 = 2 00100 2^2 = 4 01000 2^3 = 8 10000 2^3 = 16

    如图示,所有 2 的幂次方最多只有一位为 1

    明白了这一点, 我们的思路就简单了,由于符合 2 的幂次方的数只有一位为 1,x & (x-1) 是把最后一位 1 置为 0,所以只要做一次 x & (x-1) 运算,看它的值是否等于 0 即可,如果是 0 说明它可以用 2 的幂次方表示,否则不可以,代码如下:

    if(x&(x-1)) { //使用与运算判断一个数是否是2的幂次方 printf("%d不是2的幂次方!n", num); } else { printf("%d是2的%d次方!n", num, log2(num)); }

    只用一行代码即可搞定,方便了很多!

    3、 leetcode 232

    给定一个非负整数 num. 对于 0 ≤ i ≤ num 范围中的每个数字 i, 计算其二进制数中 1 的数目并将它们作为数组返回。输入: 5 输出: [0,1,1,2,1,2]

    这题的常规解法相信大家都能猜到,就是从 0 到 num 循环一遍,求出每个数字 i 中 1 的数目。

    如果用位运算怎么做呢,先来看下解法,然后我们再来分析为啥这样写,非常巧妙!

    Python 代码

    vector<int> countBits(int num) { vector<int> bits(num+1, 0); for (int i = 1; i<= num; i++) { bits[i] += bits[i & (i-1)] + 1; } }

    Java 代码

    publicstaticint[] countBits(int num) { int[] bits = newint[num+1]; Arrays.fill(bits, 0); for (int i = 1; i <= num; i++) { bits[i] = bits[i & (i-1)] + 1; } return bits; }

    最关键的代码看这一行

    bits[i] += bits[i & (i-1)] + 1;

    这行代码是啥意思呢,i & (i-1) 是把 i 的最后一个值为 1 的位设为 0,不难发现整数「i & (i-1)」 中 1 的位数比 i 中 1 的位数少一个 ,所以要加 1(即 bits[i & (i-1)] + 1)。

    非常巧妙,这样从 1 开始走一遍循环即可,中间不要做任何针对变量 i 的 1 的个数的计算,只不过付出了一个 bits 数组的代价。这里也是利用了以空间换时间的思想。

    4、 利用位运算来解八皇后问题

    接下来我们来看看终级 Boss 题,如何用位运算来解八皇后问题,解题中运用到了非常多的位运算技巧,相信你学完会收获不少。

    在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法

    举个简单的下图所示的例子,如果在棋盘上放置一个皇后,则与这个皇后同一行,同一列,且皇后所在斜线经过的格子不能再放其他皇后。

    307be76403d078b4da3f4783e6f6cd27.png

    如图示,在其中任意一行放置一个皇后,则与此皇后同行,同列,同对角线的都不允许再放其他皇后,图中蓝色区块不允许放其他皇后。

    一般我们用回溯法解八皇后。这里简单介绍一下啥是回溯法。

    在第一行从左到右先选择一个位置放置皇后,由于第一行放了皇后,第二行可放皇后的位置受到了限制(下图蓝色区块表示对应行的格子不能放皇后)

    307be76403d078b4da3f4783e6f6cd27.png

    如图示,第二行放皇后的位置只能从第三个格子开始选

    第二行我们选第三个格子放皇后,选完开始在第三行选,第三方可选的位置也受到了第一,二行皇后所放位置的影响

    307be76403d078b4da3f4783e6f6cd27.png

    如图示,第三行只能从第五个格子开始放置皇后

    同理,第三,四,五行都从左到右选择符合条件的的格子放置皇后,选完后问题来了,第六行所在行没有可选的位置了!如图示

    怎么办呢,回溯!重新摆放第五行的皇后,将其放到第八格上

    重新摆放后发现第六行还是没有符合条件的格子,咋办,我们知道第六行可摆放皇后的格子受第五行影响,而第五行受第四行摆放皇后位置的影响的,所以回溯到第四行,将皇后位置摆放到当前行的其他位置(第七格),再接着往下放 5,6,7,8 行的皇后。。。,只要不满足条件,改变上一层的的条件重新来,上一层调整后还是不符合条件,再调整上上层的。。。,调整完后重新往下递归选择,直到找到符合条件的,找到之后再在第一层换一个位置选皇后递归往下层选择执行,直到找到所有的解,这种不满足条件就回退上层调整再试的思想就是回溯法,可以看到回溯法一般是用递归实现的。

    回溯算法有不少变种,这里我们重点介绍使用位运算的回溯算法,它是所有解法中最高效的!如果在面试中能使用位运算来解回溯算法,绝对会让面试官给你个大大的赞!

    接下来是重点了,怎么用位运算来求解。

    在以上回溯法的分析中,我们不难发现,在八皇后问题中,问题的关键是找出行可放皇后的格子。找到之后问题就解决了 90%,所以接下来我们就来看看怎么找这些可用的格子。

    假设我们要求解第三行可放皇后的格子(说明一二行的皇后已放好了)那么第三行可放皇后的位置受到哪些条件的限制呢。显然在第一二行已放皇后的格子所在的列,左斜线,右斜线对应的方格都不能放皇后,如图示:

    307be76403d078b4da3f4783e6f6cd27.png

    我们以 column 来记录所有上方行已放置的皇后导致当前行格子不可用的集合,所在列如果放了皇后,则当前行格子对应的位置为 1,否则为 0,同理,以 pie(撇,左斜线) 记录所有已放置的皇后左斜方向导致当前行格子不可用的集合, na(捺,右斜线) 表示所有已放置的皇后右斜方向导致当前行不可用的集合。则对于第三行来说我们有:

    column = 10010000 (上图中的第一个图,第 1,4 列放了皇后,所以 1,4 位置为 1,其他位置为 0) pie = 00100000 (上图中的第二个图,左斜线经过第三行的第三个方格,所以第三位为 1) na = 00101000 (上图中的第三个图,右斜线经过第三行的第三, 五个方格,所以第三,五位为 1)

    将这三个变量作或运算得到结果如下

    10010000 | 00100000 | 00101000 ----------------------- 10111000

    也就是说对于第三层来说第 1,3,4,5 个格子不能放皇后。如图示

    307be76403d078b4da3f4783e6f6cd27.png

    于是可知 column | pie | na 得到的结果中值为 0 的代表当前行对应的格子可放皇后, 1 代表不能放,但我们想改成 1 代表格子可放皇后, 0 代表不可放皇后,毕竟这样更符合我们的思维方式,怎么办,取反不就行了,于是我们有~(column| pie | na)。

    问题来了,这样取反是有问题的,因为这三个变量都是定义的 int 型,为 32 位,取反之后高位的 0 全部变成了 1,而我们只想保留低 8 位(因为是 8 皇后),想把高位都置为 0,怎么办,这里就要用到位运算的黑科技了

    ~(column | pie | na) & ((1 << 8)-1)

    后面的的 ((1<< 8) -1) 表示先把 1 往左移 8 位,值为 100000000,再减 1 ,则低 8 位全部为 1,高位全部为 0!(即 0011111111)再作与运算,即可保留低 8 位,去除高位。

    费了这么大的劲,我们终于把当前行可放皇后的格子都找出来了(所有位值为 1 的格子可放置皇后)。接下来我们只要做个循环,遍历所有位为 1 的格子,每次取出可用格子放上皇后,再找下一层可放置皇后的格子,依此递归下去即可,直到所有行都遍历完毕(递归的终止条件)。

    还有一个问题,已知当前行的 column,pie,na,怎么确定下一行的 column,pie,na 的值(毕竟选完当前行的皇后后,要确定下一行的可用格子,而下一行的可用格子依赖于 column,pie,na 的值)

    上文可知,我们已经选出了当前行可用的格子(相应位为 1 对应的格子可用),假设我们在当前行选择了其中一个格子来放置皇后,此位置记为 p(如果是当前行的最后一个格子最后一个格子,则值为 1,如果放在倒数第二个,值为 10,倒数第三个则为 100,依此类推),则对于下一行来说,显然 column = column | p

    那么 pie 呢,仔细看下图,显然应该为 (pie | p) << 1, 左斜线往下一行的格子延展时,相当于左移一位,

    307be76403d078b4da3f4783e6f6cd27.png

    如图示:下一行的 pie 显然为 (pie | p) << 1。

    同理 下一行的 na 为 (na | p) >> 1。

    有了以上详细地解析,我们就可以写出伪代码了

    void queenSettle(row, colomn,pie,na) { N = 8; // 8皇后 if (row >= N) { // 遍历到最后一行说明已经找到符合的条件 count++; return } // 取出当前行可放置皇后的格子 bits = (~(colomn|pie|na)) & ((1 << N)-1) while(bits > 0) { // 每次从当前行可用的格子中取出最右边位为 1 的格子放置皇后 p = bits & -bits // 紧接着在下一行继续放皇后 queenSettle(row+1, colomn | p, (pie|p) << 1, (na | p) >> 1) // 当前行最右边格子已经选完了,将其置成 0,代表这个格子已遍历过 bits = bits & (bits-1) } }

    一开始传入 queenSettle(0,0,0,0) 这样即可得到最终的解。伪代码写得很清楚了

    展开全文
  • 版权声明:本文为苦逼的码农原创。未经同意禁止任何形式转载,特别是那些...不过,最重要的不是看懂了这些例子就好,而是要在以后多去运用位运算这些技巧,当然,采用位运算,也是可以装逼的,不信,你往下看。我...

    版权声明:本文为苦逼的码农原创。未经同意禁止任何形式转载,特别是那些复制粘贴到别的平台的,否则,必定追究。欢迎大家多多转发,谢谢

    0d1ebe846d6ee989d8a90aa3bdc7456e.png

    公众号:苦逼的码农

    作者:帅地

    位算法的效率有多快我就不说,不信你可以去用 10 亿个数据模拟一下,今天给大家讲一讲位运算的一些经典例子。不过,最重要的不是看懂了这些例子就好,而是要在以后多去运用位运算这些技巧,当然,采用位运算,也是可以装逼的,不信,你往下看。我会从最简单的讲起,一道比一道难度递增,不过居然是讲技巧,那么也不会太难,相信你分分钟看懂。

    判断奇偶数

    判断一个数是基于还是偶数,相信很多人都做过,一般的做法的代码如下

    if( n % 2) == 01
        // n 是个奇数
    }


    如果把 n 以二进制的形式展示的话,其实我们只需要判断最后一个二进制位是 1 还是 0 就行了,如果是 1 的话,代表是奇数,如果是 0 则代表是偶数,所以采用位运算的方式的话,代码如下:

    if(n & 1 == 1){
        // n 是个奇数。
    }

    有人可能会说,我们写成 n % 2 的形式,编译器也会自动帮我们优化成位运算啊,这个确实,有些编译器确实会自动帮我们优化。但是,我们自己能够采用位运算的形式写出来,当然更好了。别人看到你的代码,我靠,牛逼啊。无形中还能装下逼,是不是。当然,时间效率也快很多,不信你去测试测试。

    2、交换两个数

    交换两个数相信很多人天天写过,我也相信你每次都会使用一个额外来变量来辅助交换,例如,我们要交换 x 与 y 值,传统代码如下:

    int tmp = x;
    x = y;
    y = tmp;

    这样写有问题吗?没问题,通俗易懂,万一哪天有人要为难你,不允许你使用额外的辅助变量来完成交换呢?你还别说,有人面试确实被问过,这个时候,位运算大法就来了。代码如下:

    x = x ^ y   // (1)
    y = x ^ y   // (2)
    x = x ^ y   // (3)

    我靠,牛逼!三个都是 x ^ y,就莫名交换成功了。在此我解释下吧,我们知道,两个相同的数异或之后结果会等于 0,即 n ^ n = 0。并且任何数与 0 异或等于它本身,即 n ^ 0 = n。所以,解释如下:

    把(1)中的 x 带入 (2)中的 x,有

    y = x^y = (x^y)^y = x^(y^y) = x^0 = x。 x 的值成功赋给了 y。

    对于(3),推导如下:

    x = x^y = (x^y)^x = (x^x)^y = 0^y = y。

    这里解释一下,异或运算支持运算的交换律和结合律哦。

    以后你要是别人看不懂你的代码,逼格装高点,就可以在代码里面采用这样的公式来交换两个变量的值了,被打了不要找我。

    讲这个呢,是想告诉你位运算的强大,让你以后能够更多着去利用位运算去解决一些问题,一时之间学不会也没事,看多了就学会了,不信?继续往下看,下面的这几道题,也是非常常见的,可能你之前也都做过。

    3、找出没有重复的数

    给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数 。

    这道题可能很多人会用一个哈希表来存储,每次存储的时候,记录 某个数出现的次数,最后再遍历哈希表,看看哪个数只出现了一次。这种方法的时间复杂度为 O(n),空间复杂度也为 O(n)了。

    然而我想告诉你的是,采用位运算来做,绝对高逼格!

    我们刚才说过,两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身,所以我们把这一组整型全部异或一下,例如这组数据是:1,  2,  3,  4,  5,  1,  2,  3,  4。其中 5 只出现了一次,其他都出现了两次,把他们全部异或一下,结果如下:

    由于异或支持交换律和结合律,所以:

    1^2^3^4^5^1^2^3^4 = (1^1)^(2^2)^(3^3)^(4^4)^5= 0^0^0^0^5 = 5。

    也就是说,那些出现了两次的数异或之后会变成0,那个出现一次的数,和 0 异或之后就等于它本身。就问这个解法牛不牛逼?所以代码如下

    int find(int[] arr){
        int tmp = arr[0];
        for(int i = 1;i         tmp = tmp ^ arr[i];
        }
        return tmp;
    }

    时间复杂度为 O(n),空间复杂度为 O(1),而且看起来很牛逼。

    4、m的n次方

    如果让你求解 m 的 n 次方,并且不能使用系统自带的 pow 函数,你会怎么做呢?这还不简单,连续让 n 个 m 相乘就行了,代码如下:

    int pow(int n){
        int tmp = 1;
        for(int i = 1; i <= n; i++) {
            tmp = tmp * m;
        }
        return tmp;
    }

    不过你要是这样做的话,我只能呵呵,时间复杂度为 O(n) 了,怕是小学生都会!如果让你用位运算来做,你会怎么做呢?

    我举个例子吧,例如 n = 13,则 n 的二进制表示为 1101, 那么 m 的 13 次方可以拆解为:

    m^1101 = m^0001 * m^0100 * m^1000。

    我们可以通过 & 1和 >>1 来逐位读取 1101,为1时将该位代表的乘数累乘到最终结果。直接看代码吧,反而容易理解:

    int pow(int n){
        int sum = 1;
        int tmp = m;
        while(n != 0){
            if(n & 1 == 1){
                sum *= tmp;
            }
            tmp *= tmp;
            n = n >> 1;
        }

        return sum;
    }

    时间复杂度近为 O(logn),而且看起来很牛逼。

    这里说一下,位运算很多情况下都是很二进制扯上关系的,所以我们要判断是否是否位运算,很多情况下都会把他们拆分成二进制,然后观察特性,或者就是利用与,或,异或的特性来观察,总之,我觉得多看一些例子,加上自己多动手,就比较容易上手了。所以呢,继续往下看,注意,先别看答案,先看看自己会不会做。

    5、找出不大于N的最大的2的幂指数

    传统的做法就是让 1 不断着乘以 2,代码如下:

    int findN(int N){
        int sum = 1;
       while(true){
            if(sum * 2 > N){
                return sum;
            }
            sum = sum * 2;
       }
    }

    这样做的话,时间复杂度是 O(logn),那如果改成位运算,该怎么做呢?我刚才说了,如果要弄成位运算的方式,很多时候我们把某个数拆成二进制,然后看看有哪些发现。这里我举个例子吧。

    例如 N = 19,那么转换成二进制就是 00010011(这里为了方便,我采用8位的二进制来表示)。那么我们要找的数就是,把二进制中最左边的 1 保留,后面的 1 全部变为 0。即我们的目标数是 00010000。那么如何获得这个数呢?相应解法如下:

    1、找到最左边的 1,然后把它右边的所有 0 变成 1

    687a3ea69f36e06d19f01cd225e3aa01.png

    2、把得到的数值加 1,可以得到 00100000即 00011111 + 1 = 00100000。

    3、把 得到的 00100000 向右移动一位,即可得到 00010000,即 00100000 >> 1 = 00010000。

    那么问题来了,第一步中把最左边 1 中后面的 0 转化为 1 该怎么弄呢?我先给出代码再解释吧。下面这段代码就可以把最左边 1 中后面的 0 全部转化为 1,

    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;

    就是通过把 n 右移并且做运算即可得到。我解释下吧,我们假设最左边的 1 处于二进制位中的第 k 位(从左往右数),那么把 n 右移一位之后,那么得到的结果中第 k+1 位也必定为 1,然后把 n 与右移后的结果做或运算,那么得到的结果中第 k 和 第 k + 1 位必定是 1;同样的道理,再次把 n 右移两位,那么得到的结果中第 k+2和第 k+3 位必定是 1,然后再次做或运算,那么就能得到第 k, k+1, k+2, k+3 都是 1,如此往复下去….

    最终的代码如下

    int findN(int n){
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8 // 整型一般是 32 位,上面我是假设 8 位。
        return (n + 1) >> 1;
    }

    这种做法的时间复杂度近似 O(1),重点是,高逼格。

    总结

    上面讲了 5 道题,本来想写十道的,发现五道就已经写了好久了,,,,十道的话,怕你们也没耐心写完,而且一道比一道难的那种,,,,。

    不过呢,我给出的这些例子中,并不是让你们学会了这些题就 Ok,而且让你们有一个意识:很多时候,位运算是个不错的选择,至少时间效率会快很多,而且高逼格,装逼必备。所以呢,以后可以多尝试去使用位运算哦,以后我会再给大家找些题来讲讲,遇到高逼格的,感觉很不错的,就会拿来供大家学习了。

    你可能会喜欢

    1、【小白成长】这几年看过的优秀书籍介绍

    2、【面试被虐】游戏中的敏感词过滤是如何实现的?

    3、一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等各种锁 ---- 不看后悔系列

    4、腾讯面试:一条SQL语句执行得很慢的原因有哪些?---不看后悔系列

    5、为什么你学不会递归?告别递归,谈谈我的一些经验

    be9b681e8d816b7b34617c3abea2f508.png

    展开全文
  • 知识框架:Java运算符概述运算符是指明对操作数的运算方式。组成表达式的Java操作符有很多种(什么是操作数和操作符,例如1+2,其中1和2都是操作数,+是操作符,操作符和操作数联合起来构成表达式)...

    dacfe956b138f6f6bfd59d87c6fe2f13.png

    不闲聊!!!不扯淡!!!小UP只分享Java相关的资源干货

    Java运算符概述

    本章节目标:

    掌握常见的Java运算符的使用,包括算术运算符、关系运算符、逻辑运算符、赋值运算符、条件运算符、字符串连接运算符。

    知识框架:

    4acebe702a5a93a5cf3b4f40f613ca1d.png

    Java运算符概述

    运算符是指明对操作数的运算方式。组成表达式的Java操作符有很多种(什么是操作数和操作符,例如1+2,其中1和2都是操作数,+是操作符,操作符和操作数联合起来构成表达式)。运算符按照其要求的操作数数目来分,可以有单目运算符(1个操作数)、双目运算符(2个操作数)和三目运算符(3个操作数)。运算符按其功能来分,有算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、条件运算符、字符串连接运算符和其他运算符。常见的运算符如下所示:

    算术运算符+、-、*、/、%(取模)、++(自加1【单目】)、--(自减1【单目】)
    关系运算符>、>=、<、<=、==、!=
    逻辑运算符&(逻辑与)、|(逻辑或)、!(逻辑非)、&&(短路与)、||(短路或)
    赋值运算符=、+=、-=、*=、/=、%=、^=、&=、|=、<<=、>>=
    位运算符&(按位与)、|(按位或)、^(按位异或)、~(按位取反【单目】)、<<(左移)、>>(带符号右移)、>>>(无符号右移)
    条件运算符布尔表达式?表达式1:表达式2 (三目)
    字符串连接运算符+
    其它运算符instanceof、new

    每个编程语言当中都有运算符,基本上都是通用的,这么多的运算符,它们同时出现的时候有优先级吗?答案是有的。那么如果不确定它们的优先级怎么办,其实很简单,直接加小括号就可以了,添加有小括号优先级一定是高的,所以优先级不需要死记硬背,不确定就加小括号,例如:1 + 2 * 3,想确保先求和,你就需要这样写:(1+2)*3。

    学习本文之前建议先看:

    Java十四天零基础入门-Java基本数据类型转换zhuanlan.zhihu.com

    下一篇文章学习地址:

    Java十四天零基础入门-Java算术运算符zhuanlan.zhihu.com

    更多相关Java视频教程资料:

    2020最新版Java视频学习路线图-学习Java独孤九剑总纲篇zhuanlan.zhihu.com
    5159262ae58b90fa4bc865f7067a3938.png

    你的素质五连就是小UP的动力

    27ad63fb190c6940f3c9194206fecab2.png
    展开全文
  • a = 5 b = 7 a,b = b,a print(a,b) --------------------- [output]: 7 5 简单的加密也可以用异或运算,比如实际密码是password,既怕忘了又怕直接写下来被别人看到,就可以用一个简单的key作为密钥,两者作异或...

    目录

    1 二进制

    2 原码、反码、补码

    3 位运算符

    4 位运算符使用技巧

    上回学习运算符时,漏了位运算符,因为位运算符理解起来稍微有点复杂,所以要单独写一篇~

    要理解按位运算符,要先了解计算机进行存储和计算的底层逻辑。

    因此我们从最基础的二进制说起。

    1 二进制

    只要学过计算机,就不可能不知道二进制。

    我们知道,十进制是逢十进一,譬如11,左边的1在十位上,代表10,右边的1在个位上,就是1。

    把1502这个数字拆开看,就是有1个1000,5个100,0个10,2个1,

    ,也就是说,十进制中的位数对应的就是10的幂,个位是0次幂,十位是1次幂,百位是2次幂,以此类推……

    同理,二进制中的位数对应的就是2的幂,那么对于二进制下的1010,转化成十进制下的数,就是

    用2进制数数,首先是0,然后是1,接下去是10,而不是2,因为二进制中只有0和1。

    小白可以练习一下从0写到10,写完对一下结果:

    e372737aa8356df89642b051c11955c8.png

    2 原码、反码、补码

    这三个码的产生,都和表示减法(负数)有关,他们的正数表示完全一样

    至于为什么为了表示个负数,出现了三个码,我们一个一个来说。

    2.1 原码

    日常生活中我们用负号(减号)解决了负数的表示问题,但在计算机中怎么加上这个负号呢?人们就想了个办法,用最高位存放符号,正数为0, 负数为1

    以4位二进值数为例,最高位是符号位,那么后面只有三位来表示数字。例如,0001表示1,要表示-1,就把最高位写成1,得到1001。

    当用8位来表示一个整数时,从右往左数的第8位即为符号位,当用16位来表示一个整数时,从右往左数的第16位即为符号位。我为了少写点数字>.<,本文举例都用4位。

    61008fc6dce1852fe3848c10dd063824.png

    这种方法简单直观,但在减法运算中有问题。计算1减去1,就是0001和1001加起来,会得到1010,这是咋了?1加-1,等于-2?

    因此原码无法进行减法运算。

    2.2 反码

    正数的反码和正数的原码完全一样,对负数原码的非符号位取反,就得到负数的反码(其实也就是将正数的反码统统取反),譬如+1的反码是0001,-1的反码就是1110。

    52150087bbc2c16dedadcd11b6945731.png

    此时我们计算+1和-1相加,即0001+1110=1111,正好是-0,相反数相加等于0,没有问题。

    但此时一个0有了两种表示法,0000和1111,一个数两种表示,有点奇怪。

    除此之外,虽然相反数相加没有问题,但是其他数的减法依旧不对劲,譬如0010+1110,等于10000,最高位1溢出,就是0000,所以2-1=0???

    所以,这个码还是不行。

    2.3 补码

    正数的补码和正数的原码完全一样,负数的补码等于负数的反码+1。但是,反码加1并不是补码的真正来历,只不过补码恰好等于反码加1,这么计算更加方便而已。

    9e2ff166324d6dd6830bc50048464beb.png

    关于补码的本质和定义,其实初看难以理解,但是仔细想想,会发现这就是自然而然、浑然天成的东西。

    我很喜欢一个词,“十方圆满”,一直以来我的微信签名都是这四个字。那么,这个词讲的是怎样一种状态呢?

    • 譬如,我们原地转个圈,就可以回到原点。
    • 譬如,我们在地球上,一直往东走可以到达的地方,一直往西走,也可以到达。
    • 譬如,我们把时钟的时针往前拨180度,和往后拨180度,得到的结果是一样的。
    • 再譬如,爱因斯坦说,如果人能看到无限远,那么他就能看到自己的后脑勺。

    我到底在说什么呢?

    对于四位二进制数,最大只能存放4位,就只有0000-1111这么大的空间,就只用$2^4=16$种排列组合的方式,空间是有限的。那么,这个空间圆不圆满呢?我们想办法让它从线性变成圆就好了,理解它,就像是理解24:00就是00:00,360°就是0°一样。

    先不管负数,假设我们有一条绳子,上面从左到右依次写着0、1、2…15、16,就像这样。

    38bf52b53a5a6652c71291a75aed1097.png

    我们把绳子首尾相连,也就是把写着0和16的两端拧到一起,圈成一个圆。

    5d09abfeb6c7a8fe22047db95a30b246.png

    这个圆上只有16个数字,16就等于0,这是为了方便后面和四位二进制数的16种排列组合相对应。

    我们从0出发,顺时针走1个单位,得到1,逆时针走1个单位,得到15,1+15=16。同样的,顺时针走7个单位得到7,逆时针走7个单位得到9,7+9=16。这个16有个专业的叫法,叫做

    这里的模是什么意思呢?简单举几个例子: - 24小时制下,24就是模。 - 转一圈360°,360就是模。 - 表上有12个刻度,12就是模。

    8f9784ba5506e8b86a3e4f5ba232dc63.png

    现在看这个圆,从0开始顺时针转,数值越来越大,最后到15,再转一个单位,就又回到0,没有什么问题吧?

    好,我们开始引入负数,并且把顺时针看成加法,把逆时针看成减法,这下会得到什么呢?

    顺时针走1个单位,认为是加1,所以得到1,圆的右边还是1~7。逆时针走1个单位,就是减1,得到-1,同理,把圆的左边都填满,得到下图。

    66a0d96f2406552c036b3d9aaf981e59.png
    • 首先,距离0相同距离的数,相加等于0,这解决了相反数相加为0的问题;
    • 其次,这个圆可以把减法转化成加法,a-b=c,其实等同于a+(16-b)=c,因为模是16。可以自己验证下,譬如1-2=-1(逆时针走2个单位),在这里就等价与1+14=-1(顺时针走14个单位)。

    完美啊!接下来,我们把这个圆上的十进制数字,替换成二进制就好了。

    3ce16dd334ae56e23db4d7e3cad9a4eb.png

    问题来了,正数的二进制码毫无疑问,负数的二进制码要怎么推出来呢?还有,1对面那个问号应该是什么数字?在7和-7之间,应该是8呢,还是-8呢?

    先不管那个问号是什么数,7的二进制码是0111,再加1,得到1000,问号处的二进制码应该是1000,再加1,就是10001,以此类推,我们就能补满整个圆上的码。

    582a61bcd468013903626a9394c0f624.png

    补完你会发现,-1本就该是1111啊!因为1111再加1,为10000,但因为四位,最高位的1溢出了,所以就得到0000,-1加1可不就是0吗!

    相应的,我们可以验证-4加4,即1100+0100,等于10000,溢出位不算,0000啊!这种表示法下,所有的相反数相加都是0000~

    再看看别的减法呢,譬如6-3,即0110+1101,等于10011,即0011,就是3~哈哈,都通过验证了呢!

    现在就剩最后一个问题了,就是0的对面应该是什么?从7出发,加1应该是8,但是从-7出发,减1应该是-8,这个1000到底代表哪个数?

    其实不难发现,圆的右边都是正数,最高位皆为0,左边都是负数,最高位皆为1,这有点像原码中人为定义的最高位是符号位,所以1000自然而然应该是-8。

    虽然求补码的过程中没有特意留出一个符号位,但最终得到的补码却可以用最高位来判断正负。补码的符号位就是这么来的。

    21a86fd01460d8e34fb668b4d8895c36.png

    比比赖赖这么多,其实求补码没那么麻烦,可以汇总成一句话:正数补码不变,求负数补码用模减去其绝对值即可。

    前面我们说过模是16,那么求a-b,其实等同于a+(16-b),所以求-b这个负数的补码,用16减b不就行了吗?比如说,求-2,就用16的二进制码减去2的二进制码。可是,四位二进制码的空间里,根本没有16这个数啊?没有就对了,因为它是模,也就是10000,在八位中16的表示是00010000。

    那么,我们计算-2的补码,其实就转化成: 10000 -0010 =1110

    2.4 小结

    总结一下:

    • 原码:将最高位作为符号位(0表示正,1表示负)。
    • 反码:如果是正数,则和原码一样;如果是负数,符号位为1,其余各位取反。
    • 补码:如果是正数,则和原码一样;如果是负数,将反码加上1。

    bf21a9984920c63f584ece8285105c20.png

    很多文章在解释补码时,都是原码→反码→补码这样的思路。

    先介绍最简单的原码,它方便人读数,但无法做减法,接着引申出反码,它是原码过渡补码的中间产物,但无法解决0的问题,最后引出补码,反码直接加1即可得到补码,这个码可以完美解决前面两个码的问题。但实际上我们也知道了补码的发展过程并不如此,之所以提供这样的思路,只是为了便于计算补码。

    关于补码的定义和本质,我解释得挺业余的,举的例子也不够严谨,主要是为了方便自己理解和记忆。要看专业的解释,就得去找权威书籍或教材来看了。

    3 位运算符

    计算机底层在存储数据的时候,都是用补码存储,位运算符就是基于补码进行的计算,包括:

    1. 位逻辑运算符: 与&,或|,异或^,取反~。
    2. 位移运算符:左移<< ,右移>> 。

    65b78cb3d832b5c47cfd293546a7e0bd.png
    a = 2
    b = 3
    print("a和b转换为二进制为:", bin(a), bin(b))
    -------------------------------------
    [output]: a和b转换为二进制为 0b10 0b11

    下面我就用2和3,也就是0010和0011举例。

    a = 0010  #2
    b = 0011  #3
    
    a&b = 0010  #2
    a|b = 0011  #3
    a^b = 0001  #1
    ~a = 1101  #-3
    
    a<<1 = 0100 #左移一位,相当于乘2,得到4
    a>>1 = 0001 #右移一位,相当于除以2,得到1
    a>>2 = 0000 #右移两位,相当于除以4,不能整除时向下取整,得到0

    4 位运算符使用技巧

    在日常工作中,用到位运算符的场景似乎不多,它能用来做什么呢?

    4.1 按位与

    通常,我们写程序判断奇偶数,是除以2看余数。现在可以用该数和1进行按位与,结果是1,就是奇数,是0,则为偶数。

    def Odd_Even(x):
        if x&1 == 1:
            print(x,'是奇数')
        else:
            print(x,'是偶数')
    
    Odd_Even(666)
    ---------------------
    [output]: '666 是偶数'

    4.2 按位或

    任意数和1按位或,可以向上求最接近的奇数。

    6|1
    ---------------------
    [output]: 7
    
    7|1
    ---------------------
    [output]: 7

    4.3 按位异或

    一个数a,另一个数b进行两次异或运算,最后结果不变,即(a ^ b) ^ b = a。

    5^7
    ---------------------
    [output]: 2
    
    5^7^7
    ---------------------
    [output]: 5

    因此用异或运算调换两个数字的值。

    a = 5
    b = 7
    a = a^b
    b = b^a
    a = a^b
    print(a,b)
    ---------------------
    [output]: 7 5

    当然Python中其实可以用一行代码就完成交换。

    a = 5
    b = 7
    a,b = b,a
    print(a,b)
    ---------------------
    [output]: 7 5

    简单的加密也可以用异或运算,比如实际密码是password,既怕忘了又怕直接写下来被别人看到,就可以用一个简单的key作为密钥,两者作异或运算,得到tip,把这个tip记到小本子里。忘记密码时,将key和tip做异或运算,就能得到原密码啦~

    password = 587645
    key = 111111
    tip = password ^ key
    print(tip)
    ---------------------
    [output]: 607610
    
    tip ^ key
    ---------------------
    [output]: 587645

    4.4 按位取反

    对一个数按位取反,等于它的相反数减1。

    ~55
    ---------------------
    [output]: -56

    对一个数两次取反,结果不变。

    ~~55
    ---------------------
    [output]: 55

    4.5 按位左移

    a左移b位,就是把a转为二进制后左移b位,后面缺位补0,相当于a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2。

    5<<2
    ---------------------
    [output]: 20

    4.6 按位右移

    a右移b位,就是把a转为二进制后右移b位,前面缺位补0,相当于a除以2的b次方,并向下取整。

    14>>2
    ---------------------
    [output]: 3

    计算机中的数是用二进制来表示的,因此位运算可以更直接、更高效地实现运算操作。对于乘2除2,二进制左右位移一下就搞定,速度非常快,所以尽量用位移来代替代码中的乘除。

    最后注意一点,在Python中只能对整数进行位运算~

    参考链接:

    1)原码、反码、补码的产生、应用以及优缺点有哪些? - 张天行的回答 - 知乎

    2)原码,反码,补码的深入理解与原理

    3)Python位运算用途以及用法

    4)js 中位运算的应用

    5)位运算简介及实用技巧(一):基础篇

    展开全文
  • 问题引入在小强学Python+OpenCV之-1.4.2裁剪一节,我们使用的是numpy数组切片功能实现图片区域的裁剪。...图像的位运算。代码编写python脚本masking.py如下:# 导入库 import numpy as np import argparse im...
  • Python-第七城市609x288 - 73KB - JPEGPython基础(10)--数字_资讯_突袭网609x288 - 33KB - JPEGpython学习笔记之运算符_第1页_web前端开发715x330 - 20KB - PNG【效率提升】Python中的并行运算1728x1080 -...
  • 发现了一个很简单快速的方法: class Solution: def singleNumber(self, nums: List[int]) -> int: return reduce(lambda x, y: x ^ y, nums) 查了一下异或运算,发现找到唯一值是异或运算在python中的主要用途之一...
  • 也就是说当代码中使用到异或运算时,都会先将两个条件进行转换,转换成二进制数据后,再进行运算。运算规则:两个操作数的同位中,如果值相同(都是0或者都是1)则为0,不同(一个是0,一个是1)则为1。java中位运算符...
  • 原博文2019-07-18 16:19 −符号 描述 运算规则 ...013781相关推荐2019-09-28 21:13 −Python python是一种跨平台的计算机程序设计语言,是一种面向对象的动态类型语言。 最初被设计用于编写自动化脚本(shell),随着...
  • a =[]b=[]for i in range(100):if i & 1 == 1: #判断奇数a.append(i)elif i & 1 == 0: #判断偶数b.append(i)print('奇数列表为:', a, '\n', '偶数列表为:', b)输出结果:奇数列表为: [1, 3, 5, 7, 9, 11, 13, 15, ...
  • 论逻辑异或运算xor

    千次阅读 2013-08-07 17:21:38
    xor称作异或运算 怎么个异或法呢 很多人都没有搞清 至少,我刚开如是这样的 知道是或运算就行了 知道xor可以生成一个值,并且通过这值用同样的方式还原回去 最后 要得到结果,大都是用计算器一下 作为...
  • HDU 5416 题目链接: ... 题意: 给一棵树,每条边有权值。问f(u,v) = s的(u,v)对数有多少对。...刚开始以为是什么不知道的算法,上网看标题是树形dp或者dfs。回来想了又想……不对啊,...就算如此,怎么算两个叶子节点
  • 第七题心甘情愿不会做,解题报告的那句话不怎么看懂。 不过后来赶着做大作业去了,一直没搞。 今天稍有点轻松,于是动手解决。 琢磨着就想出来了。确实还可以的一道题。只是自己想不到。 【思路】
  • 我又来水文章了 这篇讲讲令人百思不得骑姐的异或运算让我们先来看两段,不,是10(b)段对话:技术员:这个水坝系统的设计思路为按下紧急按钮或者水位高于危险水位时,系统发出警报提问者:这个“或者”是排他的么?...
  • python3运算符,python3异或Python3运算符Python语言支持以下类型的运算符:算术运算符比较(关系)运算符赋值运算符逻辑运算符位运算符成员运算符身份运算符运算符优先级#============Python3算术运算符============...
  • 点击蓝色“五分钟学算法”关注我哟加个“星标”,一起学算法异或(^) 这个位操作运算符相信大家一定都不陌生,这个运算符可以用来解决很...解法思路 看到这样的问题,能想到的只有位运算,问题是怎么算?相信大家小学...
  • 异或(^) 这个位操作运算符相信大家一定都不陌生,这个运算符可以用来解决很多普通算法解决不了的问题...看到这样的问题,能想到的只有位运算,问题是怎么算?相信大家小学刚学习加法的时候,对于一下子不能得到答案...
  • 那么先查一下,这个异或运算到底是怎么算的吧。 关于异或运算 异或运算最能讲明白的一句话就是“同为0,异为1”,即如果两个位次的数相等,那么这一位的结果就是0,如果这两个位次的数不等,那么这一位的结果就是1...
  • 异或总结

    2019-10-09 16:14:58
    看到这样的问题,能想到的只有位运算,问题是怎么算?相信大家小学刚学习加法的时候,对于一下子不能得到答案的题,肯定会在草稿纸上列竖式,从右向左算,同一列对下来的数字相加如果超过 10,那么肯定要在下面写两...
  • 网上很多的按位异或,就是【**没有按位异与**】的,有什么库之类的或者谁能写个方法教我一下,怎么实现安慰异与?最好是C#的,谢谢 项目是之前的人留下的,没做解密工作,异或是用^,那么异与呢?求大佬帮忙
  • 算术逻辑运算单元(ALU)的基本功能为加、减、乘、除四则运算,与、或、非、异或等逻辑操作,以及移位、求补等操作。计算机运行时,运算器的操作和操作种类由控制器决定。运算器处理的数据来自存储器;处理后的结果...
  • 每个卫星测距码信号使用的唯一C/A码序列就是通过将G1序列和一定抽头选择后的G2序列模2和(modulo-2 sum,其实就是异或运算(^,xor()),就是二进制加法)产生的。 每个M序列寄存器都是10Bit,则根据M序列性质,G1序列和G2...
  • Hamming编码研究 异或⊕的本质 其实“异或”这个名词的名字...但是后来发现异或运算(同或===异或+非)满足交换律和结合律,也就是说3个及以上的bit之间也可以毫无顺序的作异或运算。那么问题来了,3个bit之间怎么...
  • 07位运算

    2020-09-13 17:18:58
    运算运算 A = 0011 1100 B = 0000 1101 A&B 0000 1100 都是1为1 A|b 0011 1101 都是0为0 A^B 0011 0001 相同为0,不相同为1(异或) ... 2*8 = 16怎么算快 位运算效率极高!!! 2*2*2*2
  • Day-5 位运算

    2021-03-23 08:23:16
    LeetCode 191.位1的个数 会这个就OK了?...进位怎么算? 当两个数都是1时,要向前进位,所以 carry(进位)= num1 & num2 << 1; 那么两个数的和 = 不进位加法的结果 + 进位 (num1 +..
  • 一哥们去笔试,回来后跟我说了一通面试题,其中有一道题让我很感兴趣:不使用+号实现加法运算刚听到后,一脸懵逼,不使用+号怎么算?问了朋友他也没做这题,不过仔细想了下,不使用+号,是否可以使用其他运算符?比如位...
  • 刚听到后,一脸懵逼,不使用+号怎么算? 问了朋友他也没做这题,不过仔细想了下,不使用+号,是否可以使用其他运算符?比如位运算符 既然说到这,就看下位运算符有哪些,实现是什么: &---位与运算符,都为真...
  • 一哥们去笔试,回来后跟我说了一通面试题,其中有一道题让我很感兴趣:不使用+号实现加法运算刚听到后,一脸懵逼,不使用+号怎么算?问了朋友他也没做这题,不过仔细想了下,不使用+号,是否可以使用其他运算符?比如位...
  • 我们会先出个位为6,十位为10,再将10+6=6。 那么在二进制里也是这样,我们先明确的知道异或是无进位加法 7=0111 9=1001 7^9=1110,这就获得了没进位前的结果 那我们怎么知道哪个被进位了,通过观察 0+0=0 1+0=1 1...
  • 首先要知道异或运算(xor)符号:⊕ 1和1、0和0异或等于0,01和10异或等于1 怎么算海明冗余码 假如数据码长度为k,再加上长度为r的校验码,r位的校验码可以表示2^ r种不同情况,那么我们要求2^ r大于k+r这样我们就...

空空如也

空空如也

1 2 3
收藏数 44
精华内容 17
关键字:

异或运算怎么算