[算法系列]位运算技巧+Leetcode习题
知识点:
-
判断2的幂
取反+1 & 原数 为原数即可
x & (-x) == x
- 如何统计一个整数里面有多少个1?
for(int j = i ; j > 0 ; j >>= 1) s += j & 1;
- 两个相同的数异或为0
- 异或的一些规律
a ^ b ^ b = a
a ^ b ^ a = b
a ^ a = 0 //这条可以用来找出只出现了一次的数
0 ^ a = a
异或满足交换律 结合律
因此交换两个数若不用额外辅助变量可如下进行:
x = x ^ y //
y = x ^ y // y = x^y = (x^y)^y = x^(y^y) = x^0 = x
x = x ^ y // x = x^y = (x^y)^x = (x^x)^y = 0^y = y
- 考察二进制数的某一位是0还是1:
(s >> k & 1)
231.2的幂
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
示例 1:
输入: 1
输出: true
解释: 2^0 = 1
示例 2:输入: 16
输出: true
解释: 2^4 = 16
示例 3:输入: 218
输出: false
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & -n) == n;
}
}
762.二进制表示中质数个计算置位
给定两个整数 L 和 R ,找到闭区间 [L, R] 范围内,计算置位位数为质数的整数个数。
(注意,计算置位代表二进制表示中1的个数。例如 21 的二进制表示 10101 有 3 个计算置位。还有,1 不是质数。)
示例 1:
输入: L = 6, R = 10
输出: 4
解释:
6 -> 110 (2 个计算置位,2 是质数)
7 -> 111 (3 个计算置位,3 是质数)
9 -> 1001 (2 个计算置位,2 是质数)
10-> 1010 (2 个计算置位,2 是质数)
- 本质就是求1的个数
class Solution {
public int countPrimeSetBits(int L, int R) {
int[] primes = {2,3,5,7,11,13,17,19};
HashSet<Integer> set = new HashSet<>();
for(int e : primes)
set.add(e);
int res = 0;
for(int i = L ; i <= R ; i ++){
int s = 0;
for(int j = i ; j > 0 ; j >>= 1) s += j & 1;
if(set.contains(s)) res ++;
}
return res;
}
}
136.只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:输入: [4,1,2,1,2]
输出: 4
public int singleNumber(int[] nums) {
int res = 0;
for(int e : nums) res ^= e;
return res;
}
476.数字的补数
给定一个正整数,输出它的补数。补数是对该数的二进制表示取反。
示例 1:
输入: 5
输出: 2
解释: 5 的二进制表示为 101(没有前导零位),其补数为 010。所以你需要输出 2 。
示例 2:输入: 1
输出: 0
解释: 1 的二进制表示为 1(没有前导零位),其补数为 0。所以你需要输出 0 。
class Solution {
public int findComplement(int num) {
int res = 0 , curPos = 0;
while( num > 0){
res += 1 - (num & 1 ) << curPos; //现在每得到一位,都要向左1位 ;"1 - " 相当于是求反
num >>= 1 ; //num向右移
curPos ++;
}
return res;
}
}
137.只出现一次的数字II
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,3,2]
输出: 3
示例 2:输入: [0,1,0,1,0,1,99]
输出: 99
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for(int bit = 0 ; bit < 32 ; bit ++){
int counter = 0;
for(int i = 0 ; i < nums.length ; i ++){
counter += (nums[i] >> bit) & 1;
}
res += (counter % 3) << bit; //找到模3余1的数,进行构造统计
}
return res;
}
}
int ones = 0 , twos = 0;
for(int x: nums){
ones = (ones ^ x) & ~twos;
twos = (twos ^ x) & ~ones;
}
return ones;
260.只出现一次的数字III
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例 :
输入: [1,2,1,3,2,5]
输出: [3,5]
注意:结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
class Solution {
public int[] singleNumber(int[] nums) {
int s = 0; //s 存得是两个数的异或和
for(int e : nums) s ^= e;
int k = 0;
while( (s >> k & 1) == 0) k ++;
int s2 = 0; //s2 存得是其中一个数
for(int e : nums){
if( (e >> k & 1) == 1) s2 ^= e;
}
/*
s = a ^ b;
b = s ^ a;
*/
return new int[]{s2, s ^ s2};
}
}
371.两整数之和
不使用运算符 + 和 - ,计算两整数 a 、b 之和。
示例 1:
输入: a = 1, b = 2
输出: 3
示例 2:输入: a = -2, b = 3
输出: 1
class Solution {
public int getSum(int a, int b) {
if(b == 0) return a;
int sum = a ^ b, carry = (a & b) << 1;
return getSum(sum , carry);
}
}
201.数字范围按位与
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
示例 1:
输入: [5,7]
输出: 4
示例 2:输入: [0,1]
输出: 0
在上图的例子中,我们可以发现,对所有数字执行按位与运算的结果是所有对应二进制字符串的公共前缀再用零补上后面的剩余位。
class Solution {
public int rangeBitwiseAnd(int m, int n) {
int move = 0;
while(m < n){
m >>= 1;
n >>= 1;
++ move;
}
return m << move;
}
}
477.汉明距离总和
两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。
计算一个数组中,任意两个数之间汉明距离的总和。
示例:
输入: 4, 14, 2
输出: 6
解释: 在二进制表示中,4表示为0100,14表示为1110,2表示为0010。(这样表示是为了体现后四位之间关系)
所以答案为:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.
思路:对于所有元素的每一位来说(竖着看),相当于从所有的0里面挑一个0,再从所有的1里面挑一个1,进行相互匹配,最后求和即可。例如:
0100 4
1110 14
0010 2
0001 1
- 只看最低位:从3个0中取一个0,有3种;1个1中取1,有一种,因此该位的汉明距离为3*1 = 3;
- 看倒数第二位:从2个0中任取一个0有2种取法,2个1中取1也是2种,该位汉明距离为2 * 2 = 4;
- …2*2 =4
- …3*1 = 3
结果相加即可。
class Solution {
public int totalHammingDistance(int[] nums) {
int res = 0;
for(int i = 0 ; i <= 30 ; i ++){ //10^9 相当于2^30数量级
int ones = 0;
for(int e : nums){
if((e >> i & 1) == 1)
ones++ ;
}
res += ones * (nums.length - ones);
}
return res;
}
}
421.数组中两个数的最大异或值
190. 颠倒二进制位
颠倒给定的 32 位无符号整数的二进制位。
示例 1:
输入: 00000010100101000001111010011100
输出: 00111001011110000010100101000000
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:输入:11111111111111111111111111111101
输出:10111111111111111111111111111111
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int res = 0;
for(int i = 0 ; i <32 ; i ++){
int tem = (n >>> i) & 1;
res |= tem << (31 - i);
System.out.println(res);
}
return res;
}
}
191.位1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。进阶:
如果多次调用这个函数,你将如何优化你的算法?
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int res = 0;
for(int i = 0 ; i < 32 ; i ++){
res += (n >> i) & 1;
}
return res;
}
}
268.丢失的数字
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
进阶:
你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中
思路:相同两个数的异或为0,异或的交换/结合律
class Solution {
public int missingNumber(int[] nums) {
int res = nums.length;
for(int i = 0 ; i < nums.length ; i ++){
int tem = i ^ nums[i];
res ^= tem;
}
return res ;
}
}
461.汉明距离
两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
给出两个整数 x 和 y,计算它们之间的汉明距离。
注意:
0 ≤ x, y < 231.示例:
输入: x = 1, y = 4
输出: 2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑上面的箭头指出了对应二进制位不同的位置。
class Solution {
public int hammingDistance(int x, int y) {
int res = 0 ;
for(int i = 0 ; i < 32 ; i ++){
int a = (x >> i) & 1 ;
int b = (y >> i) & 1;
res += Math.abs (a - b);
}
return res ;
}
}
1342.将数字变成0的操作次数
给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。
示例 1:
输入:num = 14
输出:6
解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 2) 7 是奇数,减 1 得到 6 。
步骤 3) 6 是偶数,除以 2 得到 3 。
步骤 4) 3 是奇数,减 1 得到 2 。
步骤 5) 2 是偶数,除以 2 得到 1 。
步骤 6) 1 是奇数,减 1 得到 0 。
示例 2:输入:num = 8
输出:4
解释:
步骤 1) 8 是偶数,除以 2 得到 4 。
步骤 2) 4 是偶数,除以 2 得到 2 。
步骤 3) 2 是偶数,除以 2 得到 1 。
步骤 4) 1 是奇数,减 1 得到 0 。
class Solution {
public int numberOfSteps (int num) {
int count = 0;
while(num > 0){
if((num & 1) == 0) num >>= 1;
else num -=1;
count ++;
}
return count;
}
}
169.多数元素
定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:[3,2,3]
输出:3
示例 2:输入:[2,2,1,1,1,2,2]
输出:2
public int majorityElement(int[] nums) {
int res = 0, k = nums.length >> 1;
for(int i = 0 ; i < 32 ; i ++){
int count = 0;
for(int e : nums){
count += e >> i & 1;
if(count > k){
res += 1 << i;
break;
}
}
}
return res;
}
268.丢失的数字
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
进阶:
你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
思路:
分析
由于异或运算(XOR)满足结合律,并且对一个数进行两次完全相同的异或运算会得到原来的数,因此我们可以通过异或运算找到缺失的数字。
算法
我们知道数组中有 nn 个数,并且缺失的数在 [0…n][0…n] 中。因此我们可以先得到 [0…n][0…n] 的异或值,再将结果对数组中的每一个数进行一次异或运算。未缺失的数在 [0…n][0…n] 和数组中各出现一次,因此异或后得到 0。而缺失的数字只在 [0…n][0…n] 中出现了一次,在数组中没有出现,因此最终的异或结果即为这个缺失的数字。
在编写代码时,由于 [0…n][0…n] 恰好是这个数组的下标加上 nn,因此可以用一次循环完成所有的异或运算,例如下面这个例子:
下标 0 1 2 3
数字 0 1 3 4
可以将结果的初始值设为 nn,再对数组中的每一个数以及它的下标进行一个异或运算,即:
=4∧(0∧0)∧(1∧1)∧(2∧3)∧(3∧4)
=(4∧4)∧(0∧0)∧(1∧1)∧(3∧3)∧2
=0∧0∧0∧0∧2
=2
就得到了缺失的数字为 2。
class Solution {
public int missingNumber(int[] nums) {
int res = nums.length;
for(int i = 0 ; i < nums.length ; i ++){
res ^= i ^ nums[i];
}
return res;
}
}
342.4的幂
给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4x
示例 1:
输入:n = 16
输出:true
示例 2:输入:n = 5
输出:false
示例 3:输入:n = 1
输出:true
class Solution {
public boolean isPowerOfFour(int n) {
if (n <= 0)
return false;
return ((n & (n - 1)) == 0) && //检查是否为2的幂
((n & 0xaaaaaaaa) == 0) ;
}
}
交换数字
编写一个函数,不用临时变量,直接交换
numbers = [a, b]
中a
与b
的值。示例:
输入: numbers = [1,2] 输出: [2,1]
思路:
解题思路
a ^ b ^ b = a
a ^ b ^ a = b
a ^ a = 0
0 ^ a = a
异或满足交换律 结合律
class Solution {
public int[] swapNumbers(int[] numbers) {
numbers[0] =numbers[0] ^ numbers[1];
numbers[1] = numbers[1] ^ numbers[0];
numbers[0] = numbers[0] ^ numbers[1];
return numbers;
}
}
78.子集
幂集。编写一种方法,返回某集合的所有子集。集合中不包含重复的元素。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
for(int i = 0 ; i < (1 << nums.length) ; i ++){
List<Integer> item = new ArrayList<>();
for(int j = 0 ; j < nums.length ; j ++){
if( (i >>> j & 1) == 1) item.add(nums[j]);
}
res.add(item);
}
return res;
}
}