1. 子集
class Solution {
// 二进制枚举
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
int n = nums.length;
// [0 , 1<<n)
int maxState = 1 << n;
for (int i = 0; i < maxState; i++) {
List<Integer> list = new ArrayList<>();
for (int j = 0; j < n; j++) {
// 提取第j位
int b = (i >> j) & 1;
if (b == 1) {
list.add(nums[j]);
}
}
res.add(list);
}
return res;
}
}
2. 位1的个数
public class Solution {
public int hammingWeight(int n) {
int res = 0;
while (n != 0) {
// 去除最后一个1
n = n & (n - 1);
res++;
}
return res;
}
public int hammingWeight(int n) {
int res = 0;
for (int i = 0; i < 32; i++) {
// 提取第i位
if (((n >> i) & 1) == 1) {
res++;
}
}
return res;
}
}
public class Solution {
// Lowbit理论:
// 假设x的二进制为 010100...10...000
// ~x为 101011...01...000
// -x = ~x + 1 101011...10...000
// 那么x & -x: 000000...10...000
// lowbit = n & (~n+1) = n & (-n)
// 去除最低位的1: n = n - lowbit
public int hammingWeight(int n) {
int res = 0;
while (n != 0) {
int lowbit = n & (-n+1);
n = n - lowbit;
res++;
}
return res;
}
}
3.形成两个异或相等数组的三元组数目
class Solution {
// n ^ n = 0;
// n ^ 0 = n;
// S[L,R] = S[0,L-1] ^ S[0,L-1] ^ S[L,R]= S[0,L-1] ^ S[0,R]
public int countTriplets(int[] arr) {
int res = 0;
int n = arr.length;
int[] nums = new int[n];
nums[0] = arr[0];
// 预处理异或前缀和
for (int i = 1; i < n; i++) {
nums[i] = nums[i - 1] ^ arr[i];
}
for (int i = 0; i < n; i++) {
for (int k = i + 1; k < n; k++) {
// S[L] == S[R] , S[L] ^ S[R] = 0
// S[L,R] = S[L-1]^S[R] == 0
// 必须写成nums[k],否则i-1越界
int sum = i == 0 ? nums[k] : nums[i - 1] ^ nums[k];
if (sum == 0) {
// s[i,k]=0,i->k中任意位置取j,s[i,j]=s[j-k]
res += (k - i);
}
}
}
return res;
}
}
4. 交换数字
class Solution {
public int[] swapNumbers(int[] numbers) {
// 1. 位运算 a ^ b ^ b = a
// numbers[0] = numbers[0] ^ numbers[1];
// numbers[1] = numbers[0] ^ numbers[1];
// numbers[0] = numbers[0] ^ numbers[1];
// return numbers;
// 2. 加减法
numbers[0] = numbers[0] + numbers[1];
numbers[1] = numbers[0] - numbers[1];
numbers[0] = numbers[0] - numbers[1];
return numbers;
}
}
5. 汉明距离
class Solution {
public int hammingDistance(int x, int y) {
// 方法 1:内置函数
// return Integer.bitCount(x ^ y);
// 方法2: 移位
// int xor = x ^ y;
// int res = 0;
// while (xor != 0) {
// if (xor % 2 == 1) {
// res += 1;
// }
// xor = xor >> 1;
// }
// return res;
// 方法3: 布赖恩·克尼根(Brian Kernighan)算法
int xor = x ^ y;
int res = 0;
while (xor != 0){
res ++;
// 快速去除最后一位1
xor = xor & (xor - 1);
}
return res;
}
}
6. 整数转换
class Solution {
public int convertInteger(int A, int B) {
// 方法 1:内置函数
// return Integer.bitCount(A ^ B);
// 方法2: 移位
// int xor = A ^ B;
// int res = 0;
// while (xor != 0) {
// if (xor % 2 == 1) {
// res += 1;
// }
// xor = xor >> 1;
// }
// return res;
// 方法3: 布赖恩·克尼根(Brian Kernighan)算法
int xor = A ^ B;
int res = 0;
while (xor != 0){
res ++;
// 快速去除最后一位1
xor = xor & (xor - 1);
}
return res;
}
}
7. 数字范围按位与
class Solution {
// 其实就是求m和n两个二进制字符串的公共前缀
public int rangeBitwiseAnd(int left, int right) {
// 布赖恩·克尼根(Brian Kernighan)算法:用于清除二进制串中最右边的1
// 布赖恩·克尼根(Brian Kernighan)算法的关键是对x和x-1进行按位与运算后,x最右边的1会被mo抹去变成0
// 基于上述技巧,我们可以用它来计算两个二进制串的公共前缀
// 其思想是,对于给定范围[m,n],我们可以对n迭代的应用上述技巧,清除最右边的1,直到n<=m,此时非公共前缀部分的1均被消除,因此我们最后返回n即可
// while (left < right) {
// right &= (right - 1);
// }
// return right;
int num = 0;
while (left < right) {
num++;
left = left >> 1;
right = right >> 1;
}
// 前面将left右移了num位,得到了公共前缀
// 再向左移num位,就得到了前缀不变,后缀都是0的结果
return left << num;
}
}
8. 颠倒二进制位
public class Solution {
// 取n最后一位: n&1
public int reverseBits(int n) {
int res = 0;
// 二进制反转时,如果剩余位为0,则不需要再反转了
for (int i = 31; n != 0; i--) {
res += (n & 1) << i;
// ">>" :右移,空缺位用符号位补(该方法会超时报错)
// ">>>" :无符号右移,空缺位补0
n = n >>> 1;
}
return res;
}
}
9. 两数相除
10. 两整数之和
11. 二进制求和
class Solution {
public static String addBinary(String a, String b) {
StringBuilder ans = new StringBuilder();
int ca = 0;
// 易错点: 二进制串右边是低位,字符串左边下标是0,左边是低位
for (int i = a.length() - 1, j = b.length() - 1; i >= 0 || j >= 0; i--, j--) {
int sum = ca;
sum += i >= 0 ? a.charAt(i) - '0' : 0;
sum += j >= 0 ? b.charAt(j) - '0' : 0;
// 取最后一位
ans.append(sum & 1);
// 取进位
ca = sum / 2;
}
ans.append(ca == 1 ? ca : "");
return ans.reverse().toString();
}
}
12. 格雷编码
13. 只出现一次的数字
class Solution {
// 0 ^ n = n;
// n ^ n = 0;
public int singleNumber(int[] nums) {
int res = 0;
for (int num : nums) {
res = res ^ num;
}
return res;
}
}
14. 数字的补数
class Solution {
public int findComplement(int num) {
int res = 0;
int i=0;
while(num != 0){
if(num % 2 ==0){
res = res | 1<<i;
}
i++;
num /=2;
}
return res;
}
}
class Solution {
public int findComplement(int num) {
int res = 0;
for (int i = 0; i < 31; i++) {
// 注意:最容易出错的地方,因为高位全是0,i不可能遍历完
if((1<<i) > num){
break;
}
// 提取第i位
int b = (num>>i) & 1;
if (b==0){
res |= 1<<i;
}
}
return res;
}
}
15. 只出现一次的数字
class Solution {
// 0 ^ n = n;
// n ^ n = 0;
public int singleNumber(int[] nums) {
int eor = 0;
for (int num : nums) {
eor = eor ^ num;
}
return eor;
}
}
16. 只出现一次的数字 II
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
// 依次确定每一个二进制位
for(int i = 0 ; i< 32 ; i++){
int sum = 0;
for(int num: nums){
sum += (num >> i) & 1;
}
// 答案的第i个二进制位就是数组中所有元素的第i个二进制位之和除以3的余数
if(sum % 3 !=0){
res |= 1<<i;
}
}
return res;
}
}
17. 只出现一次的数字 III
class Solution {
public int[] singleNumber(int[] nums) {
int eor = 0;
for (int num : nums) {
eor = eor ^ num;
}
// 找到eor最右边的1
int idx = 0;
for (int i = 0; i < 32; i++) {
if ((eor >> i & 1) == 1) {
idx = i;
break;
}
}
// a和b的第idx位一定是一个0一个1
int aEor = 0;
for (int num : nums) {
// 找出第idx位为1的元素进行异或,就能得到a
if ((num >> idx & 1) == 1) {
aEor = aEor ^ num;
}
}
int bEor = eor ^ aEor;
return new int[]{aEor, bEor};
}
}
18. 2的幂
class Solution {
public boolean isPowerOfTwo(int n) {
if(n<=0){
return false;
}
if((n & (n-1))==0){
return true;
}
return false;
}
}
19. 格雷编码
class Solution {
// n转格雷码: g(n) = n ^ (n>>1) 解释: 格雷码的特点是第i位为1,则第i+1位为0;第i位为0,则第i+1位为1
// 格雷码转数: int n =0 ; while( g != 0) { n ^= g ; g = g >> 1 );
public List<Integer> grayCode(int n) {
List<Integer> list = new ArrayList<>();
int i = 0;
while (i < 1 << n) {
list.add(i ^ (i >> 1));
i++;
}
return list;
}
}