网站首页 > 技术文章 正文
前言
关于位运算,相信大家都不陌生,特别是写过一些对性能要求很严苛项目的同学,毕竟,这是一把提升程序性能效率的神兵利器。
我们都知道,程序中所有的数在计算机内存中都是以二进制的形式储存的,而位运算就是直接对整数在内存中的二进制位进行操作。比如,位与,位或,异或等。
本文着重于位运算的技巧总结,难度不会太大,掌握了还可以提高自己代码的逼格,但重点是要有耐心,理解位运算的作用。
正文
判断奇偶数
先看下,我们用高级语言提供的运算如何实现:
if (n % 2) == 0) {
// n 是个奇数
} else {
// n 是个偶数
}
如果把 n 以二进制的形式展示的话,其实我们只需要判断最后一个二进制位是 1 还是 0 就行了,如果是 1 的话,代表是奇数,如果是 0 则代表是偶数,所以采用位运算的方式的话,将待判断的整数与 1 做位与操作,比如说 8 (1000) & 1 ,结果为 0,说明其为偶数,看下代码实现:
if (n & 1 == 1) {
// n 是个奇数。
} else {
// n 是个偶数
}
看到这里,可能有人会说,编译器不是会帮我们优化成位运算吗,但是,但是,算了,可以直接到 IDE 里跑一下看看时间效率的对比,哈哈哈哈。
交换两个数
交换两个数应该有很多人都写过,但是应该都是需要借助一个临时的辅助变量,代码如下:
int tmp = x;
x = y;
y = tmp;
那么,你可以不用辅助变量来实现变量的交换吗?Let's see:
x = x ^ y // (1)
y = x ^ y // (2)
x = x ^ y // (3)
可能这里大家看了一脸懵吧(如果我猜想错了大家也别打我哈哈),还是解释一下,我们这里用的是异或运算,那么异或运算是什么呢,0 ^ 1 的时候结果才为 1,其余情况都为 0,所以异或运算有一个特性,是两个不相等的数做异或运算,结果为 1,那么
- 将 (1) 代入(2),得: y = x ^ y ^ y = x ^ 0,结果为 x
- 将 (1)(2) 代入 (3), 得: x = x ^ y ^ x = y ^ 0, 结果为 y
找出没有重复的数
这个题目我在 LeetCode 中有遇到过,其他数都出现了两次,只有一个出现一次:
给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数
我们看看题干,所有的数据,只有一个数只出现一次,而其他的都是出现两次,只要这两个重点信息就可以了,通过前面的题目我们已经了解了,异或运算在两个相同的数异或时结果为 0,而其他数跟 0 异或则为自己本身,那使用位运算的解法就昭然若揭了:
int find (int[] arr) {
int tmp = arr[0];
for(int i = 1; i < arr.length; i++){
tmp = tmp ^ arr[i];
}
return tmp;
}
m 的 n 次方
第一反应是啥,是不是这样?
int pow(int n){
int tmp = 1;
for(int i = 1; i <= n; i++) {
tmp = tmp * m;
}
return tmp;
}
确实是很常规的做法,效率也不是很高,但是想一想,Java 中的 Math.pow() 是不是也是这样实现的呢?答案是否定的,Math.pow() 是 native 修饰的方法,非 Java 实现,我也没有去看过具体的 C 代码,有兴趣的同学可以到 JDK 找到对应路径的类查看研究(感觉我好欠揍)。既然是 jdk 自己使用本地方法实现的,那效率肯定不会像上面那样(O(n)),要是那样,我们每个人都可以写 Java 语言了。那我们来看一下,使用位运算的实现思路:
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),是不是效率上感觉提升了很多?离 jdk 大神又进了一步,哈哈哈哈~可能光看代码也要理解一段时间, 接下来加上文字描述,帮助大家理解代码,我们举个例子,比如 n = 15,则 n 的二进制表示为 1111,那么 m 的 15 次方可以拆解为: m ^ 1111 = m ^ 0001 * m ^ 0010 * m ^ 0100 * m ^1000, 然后我们可以通过 & 1和 >>1 来逐位读取 1101,为1时将该位代表的乘数累乘到最终结果。
找出不大于N的最大的 2 的幂指数
先看看传统做法:
int findN(int N){
int sum = 1;
while(true){
if(sum * 2 > N){
return sum;
}
sum = sum * 2;
}
}
再看看最大的 2 的幂指数,如果使用位运算的话要怎么使用,我们先看 2 的幂指数的二进制有什么特点,假设我们的数是 8 位二进制(整型一般是 32 位),那么 01000000, 00100000,00010000等等这些都是 2 的幂指数,即我们的目标,是要得到二进制数 1 的后面都为 0 的数。 目标有了,我们来看看实现的思路: 首先,我们把 n (假设为八位二进制)右边的数值全部变为 1
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
然后,再将 n + 1
n += 1;
最后,将 n 右移 1 位
n >> 1;
这样是不是就完成了找出一个数(比如 5 => 00000101,最大 2 的幂指数为 4)的最大 2 的幂指数的最大二进制数转化啦?附上完整代码:
int findN (int n) {
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
return (n + 1) >> 1;
}
总结
位运算呢,其实都是通过整型数转换成二进制去进行运算,我们需要对各种位运算的作用比较熟悉,然后拿到一道题,首先转换成二进制,看一下它有什么特点,再根据它的特点去拟运算,一开始可能会觉得比较困难,但是多看一些例子,多动手,慢慢地就能信手拈来啦~各位小伙伴加油 ~
作者:焯杰链接:https://juejin.im/post/5e3582cbe51d45580329c068
猜你喜欢
- 2024-11-09 极客算法训练笔记(十),十大经典排序之计数排序、基数排序
- 2024-11-09 Axure RP 9 学习笔记 - 常用数学函数
- 2024-11-09 判断坐标点是否在高德地图围栏内的算法?
- 2024-11-09 Quick Pow: 如何快速求幂 快速幂算法
- 2024-11-09 十六、Java运算符-优先级与表达式
- 2024-11-09 抖音B站…推荐机制的原型,威尔逊得分排序算法
- 2024-11-09 浅谈移动设备交互体验之惯性滚动 惯性移轴定理
- 2024-11-09 JS中常见的Math对象 javascript中math
- 2024-11-09 力扣73——矩阵置零 矩阵置0
- 2024-11-09 基数排序的1个小技巧,2种排序方式,3种排序算法
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-