目录
位运算
1 二进制中1的个数
剑指 Offer 15. 二进制中1的个数https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/
1. 逐位判断
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int num = 0;
while (n != 0){
num += n & 1;
n >>>= 1;
}
return num;
}
}
">>>"无符号右移 (逻辑右移)
操作规则:无论正负数,前面补零。
">>"右移
操作规则:正数前面补零,负数前面补1
"<<"左移 (向左移动n位,就相当于乘以2^n)
操作规则:无论正负数,后面补零。
左移没有<<<运算符
2. 巧用 n & (n−1)
这个运算: n & (n−1),其预算结果恰为把 n 的二进制位中的最低位的 1 变为 0 之后的结果。
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res++;
n &= n - 1;
}
return res;
}
复杂度分析:
- 时间复杂度 O(M) :n&(n−1) 操作仅有减法和与运算,占用 O(1) ;设 M 为二进制数字 n 中 1 的个数,则需循环 M 次(每轮消去一个 1 ),占用 O(M) 。
- 空间复杂度 O(1) : 变量 res 使用常数大小额外空间。
3. 分治bit求和
- 分治思想,先两两位的计算
1
的数量,再将原来数值的二进制数中的每两位都改写为这两位中1
的数量的二进制 - 代码先一一分组计算二进制
1
的个数,再两两分组,再四四分组,以此类推直到算出整个32
为int
中的bitCount
参与运算的二进制含义
0x55555555 = 0101 0101 0101 0101 ...
0x33333333 = 0011 0011 0011 0011 ...
0x0f0f0f0f = 0000 1111 0000 1111 ...
0x3f = 0011 1111
算法流程
- 行一将
i
两两分组,每组为这两位中二进制1
的个数- 按照原理,这行代码也可改写为
(i & 0x55555555) + ((i >>> 1) & 0x55555555)
- 按照原理,这行代码也可改写为
- 行二将
i
四四分组,每组为这四位中二进制1
的个数 - 行三将
i
八八分组,每组为这八位中二进制1
的个数 - 行四将
i
按十六位分组,每组为这十六位中二进制1
的个数,这里也分为了4
组,其中2
组为垃圾数据 - 行四将
i
按三十二位分组,每组为这三十二位中二进制1
的个数,这里也分为了4
组,其中3
组为垃圾数据 - 行五取出行四运算结果中的有效数据,
6
位二进制足够表示最大的32
位int
中的bitCount
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
4. 调库
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
return Integer.bitCount(n);
}
}
2 不用加减乘除做加法
剑指 Offer 65. 不用加减乘除做加法https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/
1. 迭代+异或+与运算
设两数字的二进制形式 a, b ,其求和 s = a + b,a(i) 代表 a 的二进制第 i 位,则分为以下四种情况:
观察发现,无进位和 与 异或运算 规律相同,进位 和 与运算 规律相同(并需左移一位)。因此,无进位和 n 与进位 c 的计算公式如下;
循环求 n 和 c ,直至进位 c = 0;此时 s = n,返回 n 即可。
public int add(int a, int b) {
while(b != 0) { // 当进位为 0 时跳出
int c = (a & b) << 1; // c = 进位
a ^= b; // a = 非进位和
b = c; // b = 进位
}
return a;
}
复杂度分析:
- 时间复杂度 O(1): 最差情况下(例如 a = 0x7fffffff , b = 1 时),需循环 32 次,使用 O(1)时间;每轮中的常数次位操作使用 O(1) 时间。
- 空间复杂度 O(1) : 使用常数大小的额外空间。
Q : 若数字 a 和 b 中有负数,则变成了减法,如何处理?
A : 在计算机系统中,数值一律用 补码 来表示和存储。补码的优势: 加法、减法可以统一处理(CPU只有加法器)。因此,以上方法 同时适用于正数和负数的加法 。
2. 递归
class Solution {
public int add(int a, int b) {
if (b == 0) {
return a;
}
// 转换成非进位和 + 进位
return add(a ^ b, (a & b) << 1);
}
}
大佬们的思路一个比一个清晰啊 ,惭愧我一开始还逐位用布尔值保存到栈里,,