一、移位的妙用
1.位1的个数
思路:
利用一个数和1与操作,结果就是最低位的特点,每次右移都能知道一位是不是1
public class Solution {// you need to treat n as an unsigned valuepublic int hammingWeight(int n) {int count = 0;for(int i = 0;i<32;i++){count += (n >> i) & 1;}return count; }
}
n-1
将最低位的 '1' 变为 '0',并将低位的 '0' 变为 '1',然后与 n
进行按位与运算,即可将最低位的 '1' 置为 '0'。这个算法的精妙之处在于,它不需要遍历所有的位,而是通过不断地将最低位的 '1' 置为0来计算 '1' 的个数,从而实现了高效的计算。这对于处理大量二进制数据的情况非常有用。
public class Solution {// you need to treat n as an unsigned valuepublic int hammingWeight(int n) {int count = 0;while((n != 0)){n = n&(n-1);count++;}return count; }
}
2.比特位计数
思路:从0到n循环执行求位1的个数
class Solution {public int[] countBits(int n) {int[] ans = new int[n+1];for(int i = 0;i<=n;i++){int num = i;while(num > 0){ans[i]++;num = num & (num-1);}}return ans;}
}
3.颠倒二进制位
思路:不断将 n
的最低位复制到结果的对应位置,实现了二进制位的翻转。值得注意的是,这里处理的是无符号整数,因此不需要考虑符号位,Java使用逻辑移位符号>>>
public int reverseBits(int n) {int res = 0; // 用于存储翻转后的结果int pos = 31; // 由于 int 是32位,所以从最高位开始处理,即第31位while (pos >= 0) { // 从最高位向最低位循环处理res += (n & 1) << pos; // (n & 1) 取得 n 的最低位,并将其左移 pos 位pos--; // 处理下一位n = n >>> 1; // 右移 n,去掉已经处理过的最低位}return res; // 返回翻转后的结果
}
二、位实现加减乘除专题
1.两数之和
思路:进位求和+不进位求和
进入
while
循环,只要b
不等于0,就继续执行。这个循环会一直执行,直到没有进位产生,也就是b
变成0。在循环中,首先计算进位
carry
。进位是通过将a
和b
进行按位与操作a & b
,然后将结果左移一位<< 1
得到的。这一步模拟了进位的计算。接下来,通过异或操作
a ^ b
求得不包括进位的和。异或操作可以将两个数相加,但不考虑进位。将进位
carry
的值赋给b
,以便将进位传递到下一轮循环。继续循环,直到没有进位产生,也就是
b
变成0。最后,返回
a
,即最终的和。
class Solution {public int getSum(int a, int b) {while(b != 0){int carry = (a&b)<<1;a = a ^ b;b = carry;}return a;}
}
以123+623为例:
1. 初始状态:a = 123(二进制 1111011),b = 623(二进制 1001110111)。
2. 进入循环,b 不等于 0:
- 计算进位 `carry`:(a & b) << 1 = (1111011 & 1001110111) << 1 = (1001011) << 1 = 10010110。
- 计算不包括进位的和 `a`:a = a ^ b = 1111011 ^ 1001110111 = 1001010100。
- 将进位传递给 `b`:b = carry = 10010110。
3. 进入下一轮循环,b 不等于 0:
- 计算进位 `carry`:(a & b) << 1 = (1001010100 & 10010110) << 1 = 10000000 << 1 = 100000000。
- 计算不包括进位的和 `a`:a = a ^ b = 1001010100 ^ 10010110 = 1001110010。
- 将进位传递给 `b`:b = carry = 100000000。
4. 进入下一轮循环,b 不等于 0:
- 计算进位 `carry`:(a & b) << 1 = (1001110010 & 100000000) << 1 = 100000000 << 1 = 1000000000。
- 计算不包括进位的和 `a`:a = a ^ b = 1001110010 ^ 100000000 = 1001110010。
- 将进位传递给 `b`:b = carry = 1000000000。
5. 进入下一轮循环,b 不等于 0:
- 计算进位 `carry`:(a & b) << 1 = (1001110010 & 1000000000) << 1 = 1000000000 << 1 = 10000000000。
- 计算不包括进位的和 `a`:a = a ^ b = 1001110010 ^ 1000000000 = 1001110010。
- 将进位传递给 `b`:b = carry = 10000000000。
6. 进入下一轮循环,b 不等于 0:
- 计算进位 `carry`:(a & b) << 1 = (1001110010 & 10000000000) << 1 = 0 << 1 = 0。
- 计算不包括进位的和 `a`:a = a ^ b = 1001110010 ^ 0 = 1001110010。
- 将进位传递给 `b`:b = carry = 0。
7. 进入下一轮循环,b 等于 0,循环结束。
8. 返回最终结果 `a`,即 1001110010 对应的十进制数,结果是 746。
所以,123 + 623 的结果是 746,这个算法成功地完成了加法操作。
2.递归乘法
普通解法,将乘法转换成多次加法
class Solution {public int multiply(int A, int B) {int res = 0;if(A>B){int t = A;A = B;B =t;}while(A-->0){res += B;}return res;}
}
思路:【优化】将两个数相乘的过程转化为一系列的加法和移位操作,从而减少了乘法运算的次数
n = a0*2^0 + a1*2^1 + a2*2^2 + a3*2^3........+ak*2^k
拆分成2的幂的和的方法是将乘法操作分解成了多次左移和加法操作,这是一种基于二进制的思考方式,也是快速乘法的一部分。
举例说明:13 * 12 = 13 * (8 + 4) = 13 * 8 + 13 * 4 = (13 << 3) + (13 << 2)
这个示例中,我们首先将12分解为8和4,然后用13分别乘以8和4。在二进制视角下,8可以表示为2的三次方(8 = 2^3),4可以表示为2的二次方(4 = 2^2)。因此,我们可以将这个乘法操作分解为两个左移和相加的操作。
- 首先,将13左移3位,得到13 * 8 = (13 << 3)。
- 接下来,将13左移2位,得到13 * 4 = (13 << 2)。
- 最后,将这两个部分相加,即(13 << 3) + (13 << 2),就得到了13 * 12的结果。
这种方法充分利用了二进制的特性,通过左移和相加的方式来实现乘法操作,从而提高了计算效率。
首先,将13和12转换为二进制数。13的二进制表示是1101,12的二进制表示是1100。
初始化结果为0。
从右往左,第一个数为0,进行2次幂,13*2。
第二个数的最右位是0,进行2次幂,13*2*2。
第二个数的下一位是1,乘数的二进制现在为0011,这时我们将结果加上,(最低位碰到1说明一次拆分完成要相加了)结果变为13*2*2(进行了4次加法)。
乘数对半减小0001,被乘数进行2次幂,13*2*2*2
接下来是第三位,同样是1,这时将结果再次加上13*2*2*2(8次加法),结果变为13*2*2(4次)+13*2*2*2(8次)。
最后一位是0,不进行任何操作。
现在,我们已经完成了所有位的计算,结果是13*12。
例如:14*127(11111111)/ (1+2+4+8+16+32+64)
14*127 = 14*1+14*2+14*4+14*8+14*16+14*32+14*64
14*125(11111101)= 14*1+14*4+14*8+14*16+14*32+14*64
class Solution {public int multiply(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 = min >> 1; //对半减小乘数max += max; //进行2的幂次}return ans;}
}