class 031 位运算的骚操作

news2024/10/6 23:42:16

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。

这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐.

左程云的个人空间-左程云个人主页-哔哩哔哩视频 (bilibili.com)

在这里插入图片描述

1. 题目一:判断一个整数是不是 2 的幂

1.1 2 的幂的定义是什么?

只有在二进制下, 任何位上只有一个 1, 才能是 2 的幂, 比如 0000 0001, 0001 0000, 都是 2 的幂.


要让两个2的幂相加后仍然等于2的幂,需要满足以下条件:

  1. 两个数必须是2的幂:即它们的形式必须是 (2^n) 和 (2^m),其中 (n) 和 (m) 是非负整数。

  2. 两个数的指数必须相同:即 (n = m)。


但是二进制下, 一个“二进制”位置上只能有一个是 1, 所以所有“二进制位”上只有一个 1, 才能是 2 的幂,

1.1.1 逻辑实现

“题目”的解法是:只要将最右侧的 1 提取出来, 判断是不是与本身相等就行

1.1.2 举例说明

0000 0001 这个是 2 的 0 次方 == 1; 0000 0011 这个是 2 的 0 次方加上 2 的 1 次方 == 3; 将最右侧的 1 提取出来是:0000 0001, 1 != 3; 所以 3 不是 2 的幂;

1.1.3 Brian Kernighan 算法

作用:提取出一个数字的二进制位置下的最右侧的“1”.

n & -n  // 最后可以将n这个数字的最右侧的1提取出来.

1.1.4 代码实例

// 需要会 Brian Kernighan 算法  
// 提取出二进制里最右侧的1;  
// 判断一个整数是不是2的幂  
class Solution {  
    public boolean isPowerOfTwo(int n) {  
        return n > 0 && n == (n & -n);  
    }  
}

2. 题目二:判断一个整数是不是 3 的幂

如果一个数字是3的某次幂,那么这个数一定只含有3这个质数因子
1162261467是int型范围内,最大的3的幂,它是3的19次方
这个1162261467只含有3这个质数因子,如果n也是只含有3这个质数因子,那么
1162261467 % n == 0
反之如果1162261467 % n != 0 说明n一定含有其他因子

public static boolean isPowerOfThree(int n) {  
    return n > 0 && 1162261467 % n == 0;  
}

3. 题目三:>= n 最小的 2 的幂

3.1 题目描述

已知n是非负数
返回大于等于n的最小的2某次方
如果int范围内不存在这样的数,返回整数最小值

比如说:输入的 n 是 13, 那最后返回的结果是:16, 若是输入的 n 是 4, 返回的结果是 4.
若是 n <= 0, 就直接返回 1 (2 的零次方).

3.2 解法

因为这个直接涉及到了位运算, 直接结合代码进行解释.(下面的代码可以直接复制之后直接运行)

代码实现的意义:将一个数字 n, 然后将 n - 1 这个数字的最左侧的二进制位置的 1 之后的二进制位置全部修改为 1, 然后 + 1, 这样能返回一个 2 的幂.

举一个例子:

假设:n == 45714
00000000 00000000 10110010 10010010 这个数字是:45714, 十进制表示.
先将n--;
00000000 00000000 10110010 10010001 这个数字是:45713, 十进制表示.

n >>> 1 的结果是:
00000000 00000000 01  0110010 1001000 不用管这个数字的结果是多少.不关心十进制表示.

将 n |= n >>> 1 的结果是:
00000000 00000000 11  110010 10010001 我们不关心之后的二进制位置上的数字, 因为之后的数字后续都会变成1, 对这个来说没有什么意义. 此时我们将最左侧的一个 1 右边的一个二进制位置的数字修改为了 1 , 此时最左侧和最左侧右边的一个位置的数字变成了 1 ,然后我们继续实现.

n >>> 2 的结果是:
00000000 00000000 00  111100 10100100

n |= n >>> 2 的结果是:(因为我们上一步已经将两个二进制位置的数字修改为1了, 所以此时我们移动两个位置.)
00000000 00000000 1111  0010 10010001  我们还是不关心后续二进制位置上的数字, 和原来的原因一样.

n >>> 4 的结果是:
00000000 00000000 0000  1111 00101001

将 n |= n >>> 4 的结果是:
00000000 00000000 11111111   10010001

n >>> 8 的结果是:
00000000 00000000 00000000 11111111

n |= n >>> 8 的结果是:
00000000 00000000 11111111 11111111

这样最后的n |= n >>> 16 就没有必要了, 因为:n |= n >>> 16实现之后还是原来的结果. 

00000000 00000000 11111111 11111111 
最后将这个数字 + 1.
00000000 00000001 00000000 00000000 这个就是最后的结果.这个肯定是一个2 的幂, 因为在32个二进制位置中, 只有一个1, 前面的题目也说过这个问题了.

此时先进行n--的意义也应该是有了深入的理解, 因为我们希望输入的数字本身就是2的幂的情况下, 返回这个输入的数字本身, 而不是一个更大的数字.
public class Code03_Near2power {  
  
    public static final int near2power(int n) {  
       if (n <= 0) {  
          return 1;  // 若是n <= 0, 就直接返回1 (2 的零次方).
       }  
       n--;     // 先将n--, 目的是为了能让本身就是2的幂的数最终返回自己, 比如输入4, 返回的值还是4.
       n |= n >>> 1;  
       n |= n >>> 2;  
       n |= n >>> 4;  
       n |= n >>> 8;  
       n |= n >>> 16;  
       return n + 1;  
    }  
  
    public static void main(String[] args) {  
       int number = 13;  
       System.out.println(near2power(number));  
    }  
  
}

4. 题目四:范围内所有数字 & 的结果

4.1 题目描述

在这里插入图片描述

4.2 题目解法(位运算解法)

这个暴力方法肯定是谁都会, 就不写了, 而且这个暴力方法本来也没有任何意义.

使用位运算的解法:直接结合代码进行讲解了.

注意:二进制的加减法和十进制的加减法是一一对应的, 没有任何区别. 比如:

二进制:            十进制:
01010011           19999
01000000 -         09000 -
--------           -----
00010011           10999
我们此时假设有一个数字, 此时这个数字是 right
0101001101  right 

假设此时 right == left, 那这个数字就可以直接返回了, 因为只有一个数字, 没办法 & 运算.

假设此时 right > left, 那这个数字最右侧的 1 肯定是留不下了(将来肯定会变成 0 ),
因为将 right - 1, 这个数字肯定是在(left ~ right)这个范围的.
0101001100  right - 1, 将 right 和 right - 1 做 & 运算, 最右边的一个二进制位置的结果肯定是 0 .

利用 Brian Kernighan 算法, 将 right - right & (-right), 此时保留结果.
0101001100  这个是结果. 前面的 1 都能留下来.判断一下和 left的关系, 要是 > left 继续往下走, 要是 <= left 就停止.

此时假设 right 还是 > left, 那前面结果最右侧的 1 肯定是留不下了(将来肯定会变成 0 ),
还是按照上面的方式将此时的结果 - 1
0101001011 这个是 上面的结果 - 1 的值. 这个值肯定在(left ~ right)范围内, 和上面的数字做 & 运算,最后的结果肯定是将后面两个 1 消除掉了.

所以此时将 right - right & (-right), 还是使用了 Brian Kernighan 算法, 
0101001000 这个是结果.

总结:步骤就是在 right > left 的情况下, 将 right 在二进制状态下最右侧的二进制的 1 减掉, 此时继续判断 right 和 left 的关系, 要是 right > left, 就继续将 right 在二进制状态下最右侧的二进制的 1 减掉, 要是 right <= left, 就直接停止, 返回 right. 然后继续判断 right 和 left
的关系, 其中使用到了 Brian Kernighan 算法 和 二进制的加减. 都在上面有说明.

public static int rangeBitwiseAnd(int left, int right) {  
    while (left < right) {  
       right -= right & -right;  
    }  
    return right;  
}

注意:题目 5, 6 都是大牛的实现, 所以我们只需要了解一下, 然后记住, 当成一个模板使用就行了

5. 题目五:逆序二进制的状态

5.1 题目描述

在这里插入图片描述

5.2 解法

当然可以直接用 for循环和数组, 然后一个一个地将所有二进制位置进行记录, 最后利用 | 运算返回逆序之后的数字.

5.2.1 暴力解法

这个的实现效率很慢, 所以知道就行了, 不用记住.

public static int reverseBit(int n) {  
    int[] cnts = new int[32];  
  
    for (int i = 0; i < 32; i++) {  
       cnts[i] = (n & (1 << i)) != 0 ? 1 : 0;  
    }  
  
    int ans = 0;  
    for (int i = 31; i >= 0; i--) {  
       ans |= cnts[31 - i] == 1 ? 1 << i : 0;  
    }  
    return ans;  
}

5.2.2 代码实例

看一下大牛的实现:这个是需要记住的. 将来直接使用就行了

public static int reverseBits(int n) {  
    n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1);  
    n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2);  
    n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4);  
    n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8);  
    n = (n >>> 16) | (n << 16);  
    return n;  
}

5.2.3 逻辑实现

我们先使用 8 个二进制位置进行说明:abcd efgh.

  1. 先进行 1 VS 1 的翻转:badc fehg.
  2. 然后进行 2 VS 2 的翻转:dcba hgef.
  3. 然后进行 4 VS 4 的翻转:hgef dcba.
  4. 然后可以将其拓展到 int(32位) 的情况下.

在这里插入图片描述

具体说明如何实现:如何将 1 VS 1 实现翻转:依然是:abcd efgh.

  1. 先将 abcd efgh 按位与 &10101010 这样最后的结果是:a0c0 e0g0.
  2. 然后将 abcd efgh 按位与 &01010101 这样最后的结果是:0b0d 0f0h.
  3. a0c0 e0g0 进行 >>> 1 运算, 最后结果是:0a0c0e0g
  4. 0b0d 0f0h 进行 << 1 运算, 最后结果是:b0d0f0h0
  5. 最后将两个结果进行 按位或( | ) 运算, 最后结果:badc fehg.
  6. 因为我们使用的是 8 个二进制位的, 将其扩展到 32 个二进制位, 1010 对应的是:十六进制的a, 0101 对应的是 十六进制的 5. 所以扩展到 32 位是:
    n = ((n & 0 xaaaaaaaa) >>> 1) | ((n & 0 x 55555555) << 1);.

在这里插入图片描述

如何实现:2 VS 2 的翻转:此时 n 的状态是:badc fehg.

  1. 先将 badc fehg 按位与 &11001100, 最后结果是:ba00 fe00.
  2. 然后将 badc fehg 按位与 &00110011, 最后结果是:00dc 00hg.
  3. 然后将 ba00 fe00 进行 >>> 2 操作, 结果:00ba 00fe.
  4. 然后将 00dc 00hg 进行 << 2 操作, 结果:dc00 hg00.
  5. 最后进行按位或 ( | ) 运算, 结果:dcba hgfe.
  6. 因为我们使用的是 8 个二进制位, 所以扩展到 32 个二进制位, 1100 对应的十六进制是:c, 0011 对应的十六进制是:3.

在这里插入图片描述

如何实现:4 VS 4 的翻转:此时 n 的状态是:dcba hgfe.

  1. 先将 dcba hgfe 按位与 &1111 0000, 最后结果是:dcba 0000.
  2. 然后将 dcba hgfe 按位与 &0000 1111, 最后结果是:0000 hgfe.
  3. 然后将 dcba 0000 进行 >>> 4 操作, 结果:0000 dcba.
  4. 然后将 0000 hgfe 进行 << 4 操作, 结果:hgfe 0000.
  5. 最后进行按位或 ( | ) 运算, 结果:hgfe dcba.
  6. 因为我们使用的是 8 个二进制位, 所以扩展到 32 个二进制位, 1111 对应的十六进制是:f, 0000 对应的十六进制是:0.

之后的 8 VS 8 的就不进行说明了, 经过前面的推导, 后续的实现肯定是能进行的, 自己画一下吧.

6. 二进制中有几个 1

6.1 逻辑实现

我们还是按照 8 个二进制位进行举例子:1111 1001, 我们定义一个长度, 统计每一个长度中的 1 的个数, 假设现在长度是 1,

  1. 那么 0 位置的 1 个数有 1 个,
  2. 1 位置 1 的个数有 0 个,
  3. 2 位置 1 的个数有 0
  4. 3 位置 1 的个数有 1
  5. 4 位置 1 的个数有 1
  6. 5 位置 1 的个数有 1
  7. 6 位置 1 的个数有 1
  8. 7 位置 1 的个数有 1

然后我们进行扩展, 将现在的长度变为:2, 那么:11 11 10 01

  1. 1 位置 1 的个数有 1 个,
  2. 2 位置 1 的个数有 1
  3. 3 位置 1 的个数有 2
  4. 4 位置 1 的个数有 2

用代码实现将 1 长度变为 2 长度

先将 1111 1001 & 0101 0101
最后结果是:01010001

然后我们将 1111 1001 >>> 1 -> 0111 1100 & 0101 0101
最后结果是:01010100

然后将两个状态相加:
1010 0101   此时就成了长度为 2 的情况下, 二进制中 1 的个数
10 10 01 01 
2  2  1  1   长度为 2 的情况下, 二进制中 1 的个数

然后继续使用代码表示将 2 长度迁移到 4 长度

1010 0101 这个是长度为 2 的情况下的表示

我们将 1010 0101 & 0011 0011
0010 0001 结果

然后我们将 1010 0101 >>> 2 -> 0010 1001 & 0011 0011
0010 0001 结果

将两个结果加起来
0100 0010     此时是长度为 4 的情况下, 二进制中 1 的个数

然后继续用代码表示将 4 长度迁移到 8 长度

0100 0010 这个是长度为 4 的情况下的表示

我们将 0100 0010 & 0000 1111
0000 0010 结果

然后将 0100 0010 >>> 4 -> 0000 0100 & 0000 1111
0000 0100 结果

将两个结果相加:
0000 0110 这个是长度为 8 的情况下, 二进制中 1 的个数,此时是:6 个.(2^2 + 2^1 == 6).

以此类推, 一直将长度迁移到了 int(32位) 的情况下就是最后的结果

6.2 代码实例

public static int cntOnes(int n) {  
    n = (n & 0x55555555) + ((n >>> 1) & 0x55555555);  迁移到长度为 2
    n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);  迁移到长度为 4 
    n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f);  迁移到长度为 8
    n = (n & 0x00ff00ff) + ((n >>> 8) & 0x00ff00ff);  迁移到长度为 16
    n = (n & 0x0000ffff) + ((n >>> 16) & 0x0000ffff); 迁移到长度为 32, 就是最后结果
    return n;  最后结果
}

另一个代码实例:这个用了 Brian Kernighan算法, 这个题目让统计一个数字中 32个 二进制位中所有 1 的数量,

  1. 所以我们直接将数字中最右侧的 1 提取出来, 然后用原来的数字减掉, 此时设置一个计数器 cntscnts++,
  2. 然后继续利用 Brian Kernighan算法, 继续减掉, 直到数字变成 0 停止, 这样 cnts 的值就是一个数字中所有二进制位中 1 的数量.
public static int cntOnes(int n) {  
    int cnts = 0;  
  
    while (n != 0) {  
       n -= (n & -n);  
       cnts++;  
    }  
  
    return cnts;  
}

7. 学习位运算的意义

在这里插入图片描述

位运算的常数时间是非常好的, 使用位运算可以很大程度上提高我们代码的运行速度, 而且使用的内存也很少, 在一些底层的操作上, 使用位运算是极好的. 而且位运算使用起来也会很简洁高效,

但是我们也没有必要强制使用位运算, 没有必要去在任何情况下都追求位运算的实现和使用, 尽量写到时间复杂度和空间复杂度最优, 自己能理解就行. 不要钻牛角尖.

大牛的实现我们直接当成模板用就行了,

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

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

相关文章

8649 图的广度遍历

### 思路 1. **图的邻接表存储结构**&#xff1a;使用邻接表存储图的顶点和边信息。 2. **基本操作函数**&#xff1a;包括创建图、查找顶点、获取顶点值、获取第一个邻接顶点、获取下一个邻接顶点等。 3. **广度优先遍历&#xff08;BFS&#xff09;**&#xff1a;从某个顶点出…

LPDDR6 来之未远

很多朋友可能还没用上DDR5,但不好意思的是,DDR6 可能马上就要出现了。 三星和海力士较早开始DDR6 的设计,预计2025年商业化。 DDR6 速度 来源: 半导体观察 DDR6的速度将是主流的DDR4的四倍,将是现有DDR5的两倍,DDR6传输速度可达12800 Mbps。 LPDDR6 来源:快科技 L…

OpenAI董事会主席Bret Taylor的Agent公司Sierra:专注于赋能下一代企业用户体验

本文由readlecture.cn转录总结。ReadLecture专注于音、视频转录与总结&#xff0c;2小时视频&#xff0c;5分钟阅读&#xff0c;加速内容学习与传播。 视频来源 youtube: https://www.youtube.com/watch?vriWB5nPNZEM&t47s 大纲 介绍 欢迎与介绍 介绍Bret Taylor&#x…

功耗电流图的对比技巧

电流波形对比 使用系统画图工具的反色和透明设置项目&#xff0c;就可以将2张图合在一块看 方法【系统画图工具】 例如在相同的测试用例&#xff0c;可以对比电流和耗电量的差异

3.使用条件语句编写存储过程(3/10)

引言 在现代数据库管理系统中&#xff0c;存储过程扮演着至关重要的角色。它们是一组为了执行特定任务而编写的SQL语句&#xff0c;这些语句被保存在数据库中&#xff0c;可以被重复调用。存储过程不仅可以提高数据库操作的效率&#xff0c;还可以增强数据的安全性和一致性。此…

Python3 爬虫 中间人爬虫

中间人&#xff08;Man-in-the-Middle&#xff0c;MITM&#xff09;攻击是指攻击者与通信的两端分别创建独立的联系&#xff0c;并交换其所收到的数据&#xff0c;使通信的两端认为其正在通过一个私密的连接与对方直接对话&#xff0c;但事实上整个会话都被攻击者完全控制。在中…

LCD屏入门(基于ESP-IDF、SPI屏)

主要参考资料&#xff1a; ESP32-S3 开发 SPI 屏【DIY 智能手表】: https://www.bilibili.com/video/BV1Yc411y7bb/?spm_id_from333.337.search-card.all.click&vd_sourcedd284033cd0c4d1f3f59a2cd40ae4ef9 使用 SPI 屏和 I2C 触屏运行 SquareLine Studio 提供的手表 UI 示…

突触可塑性与STDP:神经网络中的自我调整机制

突触可塑性与STDP&#xff1a;神经网络中的自我调整机制 在神经网络的学习过程中&#xff0c;突触可塑性&#xff08;Synaptic Plasticity&#xff09;是指神经元之间的连接强度&#xff08;突触权重&#xff09;随着时间的推移而动态变化的能力。这种调整机制使神经网络能够通…

链动 2+1 模式 S2B2C 商城小程序:交易转化的创新引擎

摘要 在数字化商业时代&#xff0c;电商行业竞争激烈&#xff0c;交易转化成为核心问题。链动 21 模式 S2B2C 商城小程序源码作为创新电商模式&#xff0c;通过独特的推荐与分享机制、丰富奖励机制、AI 智能名片及 S2B2C 商城的个性化定制与供应链协同等&#xff0c;在交易转化…

redis+mysql数据一致性+缓存穿透解决方案

在分布式事务中我们知道有cap定理&#xff0c;即 我们保证高可用的情况下&#xff0c;必然要牺牲一些一致性&#xff0c;在保证强一致性的情况下&#xff0c;必然会牺牲一些可用性。而我们redismysql数据一致性的使用策略就是在我们保证可用性的情况下尽量保证数据的一致性。想…

MySql的基本语法操作

查看数据库和表 查看所有的数据库 show databases; 建立一个新的数据库 create database database_name; 也可以是 create database if not exists database_name; 表示这个数据库不存在才建立 而不会打断其他sql语句的执行&#xff0c;而如果没有加的话&#xff0c;创建…

神经网络及大模型科普揭秘

一、生物神经元及神经元构成的神经网络 下图是生物神经元的示意图: 生物神经元由细胞体、树突、轴突、轴突末梢四部分构成。 下图是生物神经网络的一个简单示意图: 生物神经元通过电信号在彼此间传递信号,神经元的各个树突接收输入信号,经过细胞体汇总,如果最终总和高…

【动态规划-最长公共子序列(LCS)】力扣97. 交错字符串

给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 … sn t t1 t2 … tm |n - m| < 1 交错 是 s1…

【微服务】服务注册与发现 - Eureka(day3)

CAP理论 P是分区容错性。简单来说&#xff0c;分区容错性表示分布式服务中一个节点挂掉了&#xff0c;并不影响其他节点对外提供服务。也就是一台服务器出错了&#xff0c;仍然可以对外进行响应&#xff0c;不会因为某一台服务器出错而导致所有的请求都无法响应。综上所述&…

网络安全概述:从认知到实践

一、定义 网络安全&#xff0c;即致力于保护网络系统所涵盖的硬件、软件以及各类数据&#xff0c;切实保障其免遭破坏、泄露或者篡改等不良情形的发生。 二、重要性 个人层面&#xff1a;着重于守护个人隐私以及财产安全&#xff0c;为个人在网络世界中的各项活动提供坚实的保…

分享几个做题网站------学习网------工具网;

以下是就是做题网站&#xff1b;趣IT官网-互联网求职刷题神器趣IT——互联网在线刷题学习平台&#xff0c;汇集互联网大厂面试真题&#xff0c;拥有java、C、Python、前端、产品经理、软件测试、新媒体运营等多个热门IT岗位面试笔试题库&#xff0c;提供能力测评、面试刷题、笔…

Meta 首个多模态大模型一键启动!首个多针刺绣数据集上线,含超 30k 张图片

小扎在 Meta Connect 2024 主题演讲中宣布推出首个多模态大模型 Llama 3.2 vision&#xff01;该模型有 11B 和 90B 两个版本&#xff0c;成为首批支持多模态任务的 Llama 系列模型&#xff0c;根据官方数据&#xff0c;这两个开原模型的性能已超越闭源模型。 小编已经迫不及待…

算法修炼之路之二分查找

目录 一:三大二分介绍及模板 1.普通二分 2.查找左右边界的二分及模板 二:LeetCode OJ练习 1.第一题 2.第二题 3.第三题 4.第四题 5.第五题 6.第六题 一:三大二分介绍及模板 1.普通二分 这里通过一道题来引出普通二分及模板 LeetCode_704 二分查找 画图分析: 具体代…

AI 大模型的核心能力与应用场景全解析

深入理解 AI 大模型&#xff1a;核心能力与应用场景全解析 AI大模型是什么 通过概念考察的方式了解AI大模型&#xff0c;拆开来看。 AI领域术语丰富&#xff0c;涵盖模式识别、自然语言处理、神经网络、机器学习、深度学习、强化学习及人类反馈强化学习。大模型&#xff1a;把…

Springboot人才求职招聘系统源码(前台+后台)

文章目录 1 项目描述2 后台功能3 获取方式 项目技术&#xff1a;springboot 运行环境&#xff1a;jdk1.8idea/eclipsemaven3mysql5.6 1 项目描述 基于springboot的招聘系统&#xff0c;包括前台和后台管理系统&#xff0c;前台包括首页&#xff0c;应聘职位&#xff0c; 在…