上一篇内容中,我们总结了位运算常用的技巧,你记忆的如何了呢?我猜:你未必全部记住吧!那么,白银挑战笔记它来了,主要针对常用技巧进行实战巩固,进一步体会、理解,以至于使用位运算常用技巧来解决问题!纸上得来终觉浅,绝知此事要躬行!
在青铜挑战笔记中,我们对数字中1的内容做了三种总结,分别是:
获取第i位数据,判断是否为1【num & (1<
将最后一位数据1变成数据0【num&(num-1)】;
仅保留最后一位数据1,其余全部变成0【num&(-num)】。
就使用这三个总结的常用方法,完成“1”位移的妙用吧!
题目见LeetCode191,描述为统计int数字(二进制字符串)中数据位为1的个数。
题目分析,该怎么做?
方法一,获取数字的每一位数据,如果该位数据为1,则1的个数增加!直接上代码!
public static int myHammingWeight11(int n) {
int count = 0;
for (int i = 0; i < 32; i++) {
count += (n >> i) & 1;
}
return count;
}
方法二,将该数字的最后一位1变成0,然后统计这样操作一共进行几次,进行几次,1的个数就为几个!直接上代码!
public static int myHammingWeight2(int n) {
int count = 0;
while(n != 0){
n &= n - 1;
count++;
}
return count;
}
题目见LeetCode338,描述为给定一个整数n,返回由0到n的每个数字各包含几个1。
题目分析,题目位1的个数中,我们统计了一个数字中1的个数有几个,那么只需要遍历从0到n的所有数字,分别统计它们包含1的个数即可。厘清思路,直接上代码!
//方法1,获取第i为数字
public static int[] myCountBits(int num) {
int[] bits = new int[num + 1];
for (int i = 0; i <= num; i++) {
int count = 0;
for (int j = 0; j < 32; j++) {
bits[i] += (i >> j) & 1;
}
}
return bits;
}
//方法2,将数字的最后数据1变成数据0
public static int[] myCountBits2(int num) {
int[] bits = new int[num+1];
for (int i = 0; i <= num; i++) {
bits[i] = countNumOne(i);
}
return bits;
}
public static int countNumOne(int num){
int count = 0;
while(num != 0){
num &= num -1;
count++;
}
return count;
}
题目见LeetCode190,描述为颠倒32位无符号整数!
如何颠倒数字?答:逆向过程,栈!那,能否使用位运算?答:可以!
逻辑如下:先获取数字第i位数据(模板!!),反转后原数据第i位数据将变成新数据的第(31-i)位数据!
厘清思路,直接上代码!
public static int myReverseBits(int n) {
int res = 0;
for (int i = 0; i < 32; i++) {
res += ((n >>> i) & 1) << (31 - i);
}
return res;
}
那,还有其他方法嘛?这个方法的时间复杂度是O(n)!
答:有!使用二分位反转,即可!
什么是二分位反转?首先将32位数据分割成前16位和后16位,将这两段数据进行翻转。然后将前后16位的数据分割成前8位和后8位,对这两段数据进行反转。依次执行这个过程,直到将2位数据分成前1位和后1位,对这两段数据进行反转。结合代码,理解这个二分位反转的过程,开阔眼界!
public static int reverseBits2(int n) {
//第一次翻转
n = (n >>> 16) | (n << 16);
//第二次翻转
n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8);
//第三次翻转
n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4);
//第四次翻转
n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2);
//第五次翻转
n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1);
return n;
}
从简单的加法特殊情况出发,比如01 1010 + 00 0010,如何进行加法运算?首先是二者相加的保留位01 1000,其次是二者相加的进位位00 0100,最终结果 01 1000 + 00 0100 = 01 1100(注意:在这个过程中,仍可能出现保留位和进位位相加产生新的进位位)
那么,何时达到最终结果呢?答:没有进位位时,即进位位=0时,加法结束!
OK,加法的过程重点在于求得进位位和保留位,怎么实现?
答:保留位:两个数字为1的数据变成0,为0的数据还是0,一个数据为0另一个数据为1则变成数据1,什么运算?异或!
进位位:两个数字为1的数据结果仍然是1不过向左移位1位,什么运算?与并且左移1位!
厘清思路,直接上代码!
public static int plusByBit(int a, int b){
int sign = 0;
while(b != 0){
//进位
sign = (a & b) << 1;
//保留位
a ^= b;
b = sign;
}
return a;
}
从简单的加法特殊情况出发,比如4*3,也就是4*(0011) = 4*(0010)+4*(0001),那么,就可以从比较小的乘数出发,取出数据为1的数据,结合被乘数的倍数,完成累加计算!
什么是被乘数的倍数?简单来说,被乘数是4,乘数3决定了其在1倍和2倍的时候应该累加计算,即4*(0010+0001)=4*(2+1)!
厘清思路,直接上代码!(结合代码,更易厘清思路!)
public static int multiplyByBit(int a, int b){
int min = Math.min(a, b);
int max = Math.max(a, b);
int ans = 0;
while(min > 0){
if((min & 1) == 1){
ans += max;
}
min >>= 1;
max += max;
}
return ans;
}
OK,《算法通关村第十一关——位运算白银挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)
再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第十一关第三幕见!