位运算详解

简介: 本文介绍了位运算符及其基本操作,并通过几个例题详细解析了位运算的应用。内容包括左移`<<`、右移`>>`、按位取反`~`、与运算`&`、或运算`|`和异或运算`^`等运算符的使用方法。基本操作部分展示了如何检查和修改二进制位,以及异或运算的性质。例题部分则通过判定字符是否唯一、丢失的数字、两整数之和和消失的两个数字等问题,具体说明了位运算的实际应用技巧。

1. 运算符

<<:左移运算符,左边移出的位被丢弃,右边空出的位用 0 填充。

>>:右移运算符,将左边的操作数的二进制表示向右移动右边操作数指定的位数。

~:按位取反,将操作数的二进制表示中的每一位进行取反操作

&:与运算,有 0 结果就是 0

|:或运算,有 1 结果就是 1

^:亦或,相同为 0,相异为 1 / 不进位相加

2. 基本操作

191,338,461,136,260

给定一个数 n,确定它的二进制表示中第 x 位是 0 还是 1

(n >> x) & 1

给定一个数 n,把它的二进制表示的第 x 位修改为 1

n |= (1 << x)

给定一个数 n,把它的二进制表示的第 x 位修改为 0

n &= (~ (1 << x))

提取一个数 n 二进制表示中最右边的 1

n & -n ( -n 就是将最右侧的 1 左边全部变成相反的,右边不变)

给定一个数 n ,把最右侧的 1 变成 0

n & (n - 1)

亦或(^)运算符运算律

  1. a ^ a = 0
  2. a ^ 0 = a
  3. a ^ b ^ c = a ^ (b ^ c)

3. 例题解析

a. 面试题 01.01. 判定字符是否唯一

面试题 01.01. 判定字符是否唯一

如果使用数据结构的话很明显就是放到哈希表中,然后再遍历哈希表进行查找,如果不使用数据结构的话可以通过使用位图的方式来替代哈希表,26 个比特位代表 26 个字母(1 表示该字符已经出现过,0 就表示没有出现过),在遍历字符串的同时进行判断,如果说出现过就直接放回 false ,如果没有就把该位设置为 1

class Solution {
    public boolean isUnique(String astr) {
        if(astr.length() > 26) return false;
        char[] chars = astr.toCharArray();
        int bitMap = 0;
        for(int i = 0;i < chars.length;i++){
            int x = chars[i] - 'a';
            if((bitMap >> x & 1) == 1) return false;
            else bitMap |= (1 << x);
        }
        return true;
    }
}

b. 丢失的数字

268. 丢失的数字

方法一:使用哈希表,遍历数组,把数组中的数存在哈希表中并设为 1,再从 0 ~ n 进行遍历,查询哪一位为 0

方法二:高斯求和,把 0 ~ 1 的数进行求和,之后再把数组中的元素都减掉,最后剩的即为所求

方法三:位运算

先把 0 ~ 1的数都进行亦或运算,然后再遍历数组,再进行一次亦或运算,由于 a ^ a = 0,所以重复出现的都被变为 0 了,最后再根据 a ^ 0 = a 知道,剩余的数肯定就是答案了

class Solution {
    public int missingNumber(int[] nums) {
        int ans = 0;
        for(int i = 0;i <= nums.length;i++){
            ans ^= i;
        }
        for(int i = 0;i < nums.length;i++){
            ans ^= nums[i];
        }
        return ans;
    }
}

c. 两整数之和

371. 两整数之和

由于题中限制了不能使用 + ,- ,所以可以考虑位运算来解决,在开始提到过亦或操作可以看做是无进位相加,所以只需要把进位找到就行,经过分析发现,只有 1 和 1 的情况会出现进位,也就符合了 & 运算,但此时只是表示哪一位发生了进位,所以需要把结果左移再加上无进位相加的结果即可,不过此时还可能会出现进位,所以上述操作需要重复计算,直到没有进位为止

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

d. 只出现一次的数字 II

137. 只出现一次的数字 II

从每一个二进制位相加开始看,所有的数相加最后会有四种情况,把这些情况都 % 3 之后发现,结果和最后剩余那个只出现一次的数的位置相同,所以也就可以从每一个二进制位入手,把所有的数字加起来 % 3,得出每一位的结果,并把目标位设置为结果,最后就是只出现一次的那个数

class Solution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        for(int i = 0;i < 32;i++){
            int sum = 0;
            for(int x : nums){
                if(((x >> i) & 1) == 1){
                    sum ++;
                }
            }
            if(sum % 3 == 1) ans |= (1 << i);
        }
        return ans;
    }
}

e. 面试题 17.19. 消失的两个数字

面试题 17.19. 消失的两个数字

如果这道题转换一下,先把从 1 到 N 的数亦或,再把结果和 nums 数组亦或,结果也就变成了 nums 数组的数出现了两次,其他的数(也就是缺失的数)出现了一次,就转化为了求消失的两个数字,最终的亦或结果是缺失的那两个数的亦或结果,其中一定可以找到二进制位为 1 的那一位,也就可以把所有的数根据这个位置分为两组,这样也就能够把缺失的这两个数分开,就转化为了找缺失的一位的数,把所有结果亦或,最终就是缺失的那个数字,再求另一组即可

class Solution {
    public int[] missingTwo(int[] nums) {
        int tmp = 0;
        //把 1 ~ n 的数亦或
        for(int i = 1;i <= nums.length + 2;i++){
            tmp ^= i;
        }
        //加上数组的数亦或
        for(int i = 0;i < nums.length;i++){
            tmp ^= nums[i];
        }
        //找到第一个为 1 的二进制位
        int diff = 0;
        while(true){
            if(((tmp >> diff) & 1) == 1) break;
            else diff++;
        }
        int[] ans = new int[2];
        //数组的数分组亦或
        for(int i = 0;i < nums.length;i++){
            if(((nums[i] >> diff) & 1) == 1) ans[0] ^= nums[i];
            else ans[1] ^= nums[i];
        }
        //1 ~ n 的数分组亦或
        for(int i = 1;i <= nums.length + 2;i++){
            if(((i >> diff )& 1) == 1) ans[0] ^= i;
            else ans[1] ^= i;
        }
        return ans;
    }
}
相关文章
|
算法 测试技术 定位技术
数据结构与算法——DFS(深度优先搜索)
数据结构与算法——DFS(深度优先搜索)
|
网络安全 开发工具 数据安全/隐私保护
解决 Enter passphrase for key ‘/Users/dzm/.ssh/id_rsa‘:
解决 Enter passphrase for key ‘/Users/dzm/.ssh/id_rsa‘:
5139 0
|
SQL 缓存 监控
SpringBoot整合阿里巴巴Druid数据源
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。 Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。 Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。 本文主要讲解如何整合Druid数据源及Druid常用配置项和详解
6182 1
SpringBoot整合阿里巴巴Druid数据源
|
10月前
|
数据可视化 Linux C#
Visual Studio 高手进阶的 10 个效率与洞察力利器
本文深入解析10个Visual Studio中常被忽视的高级功能与技巧,专为资深开发者设计。内容涵盖性能剖析、调试增强、内存布局查看、自定义调试可视化、条件断点、并行调试、正则搜索、构建管理、热重载突破及WSL2集成等,助你挖掘VS潜能,显著提升开发效率与问题诊断能力,成为真正的VS忍者。
332 0
|
算法
算法】位运算——常见位运算基础操作总结
算法】位运算——常见位运算基础操作总结
621 0
算法】位运算——常见位运算基础操作总结
|
算法 Java 测试技术
二分查找(二):二分查找边界
本文深入探讨了二分查找在有序数组中查找元素边界的技巧,包括最左边界和最右边界。通过调整传统二分查找逻辑,我们可以在遇到重复元素时精确定位其范围。文章详细解析了查找左边界的方法,并介绍了两种实现右边界查找的策略:复用左边界查找和转化为查找相关元素。此外,代码示例展示了如何支持升序与降序数组,处理特殊情况如空数组或目标值不存在。最终,通过多个测试用例验证了算法的正确性和高效性。这种方法在处理大规模数据时尤为有用,是优化搜索性能的重要工具。
282 0
|
XML Java 数据库连接
如何使用 MyBatis 来进行增、删、改、查操作
如何使用 MyBatis 来进行增、删、改、查操作
842 2
|
C语言
【C语言程序设计——循环程序设计】鸡兔同笼问题(头歌实践教学平台习题)【合集】
本教程介绍了循环控制和跳转语句的使用,包括 `for`、`while` 和 `do-while` 循环,以及 `break` 和 `continue` 语句。通过示例代码详细讲解了这些语句的应用场景,并展示了如何使用循环嵌套解决复杂问题,如计算最大公因数和模拟游戏关卡选择。最后,通过鸡兔同笼问题演示了穷举法编程的实际应用。文中还提供了编程要求、测试说明及通关代码,帮助读者掌握相关知识并完成任务。 任务描述:根据给定条件,编写程序计算鸡和兔的数量。鸡有1个头2只脚,兔子有1个头4只脚。
832 5
|
Java API 数据库
详细介绍如何使用Spring Boot简化Java Web开发过程。
Spring Boot简化Java Web开发,以轻量级、易用及高度可定制著称。通过预设模板和默认配置,开发者可迅速搭建Spring应用。本文通过创建RESTful API示例介绍其快速开发流程:从环境准备、代码编写到项目运行及集成数据库等技术,展现Spring Boot如何使Java Web开发变得更高效、简洁。
382 1
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
1076 1

热门文章

最新文章