前言:刚刚接触位运算,看这个入门视频,主要是对视频内容的总结和自己思路的整理
题目由up主整理,来源于力扣官方
位运算
分为两大类
-
逻辑位运算符
位与
&
位或
|
异或
^
按位取反
~
-
位移运算符
左移
<<
:x<<y x向左偏移y位,末位补0,左移可以看成x乘2右移
>>
:向右偏移y位,如果x是非负数,则高位补0,x是负数则高位补1,可以看成是x除2并向下取整
位运算的应用
表状态,用一个整数表示两种状态
各种状态之间相互独立,可以叠加
231. 2 的幂
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
思路:&
n<0,则必然不是2的幂
如果是2的幂,那么其二进制表示必为1000…(若干个0)
如:10000 - 1 = 01111,那么有n&(n-1)
必然=0
return (n > 0) && (n & (n-1)) == 0;
342. 4的幂
2的偶数次幂mod 3 = 1;
2的奇数次幂mod 3 = 2
return n > 0 && (n & (n-1)) == 0 && n % 3 ==1;
191. 位1的个数
输入一个无符号整数,返回位1的个数
思路:&
遇到1就把1消去,记录消去的次数
1000 - 1 = 0111
,1000 & 0111 = 0000
这样原来的1就被消去了
1010 - 1 = 1001
,1010 & 1001 = 1000
每进行一次&
运算就会消去一个1
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count =0;
while(n != 0){
n &= (n-1);
count++;
}
return count;
}
}
面试题 16.01. 交换数字
编写一个函数,不用临时变量,直接交换numbers = [a, b]
中a
与b
的值。
思路:^
相同的数异或为0,任何数与0异或得它本身,满足交换律和结合律
tmp = a ^ b,这里tmp表示a和b之间的差异,ab任何与tmp进行异或都可以将另一个数还原
那么,只需要tmp和a,b中的一个,就可以还原另一个数了
这样就只需两个变量保存了
// a = a ^ b 此时a为tmp
// b = a ^ b 此时b = tmp ^ b = a
// a = a ^ b 此时a = tmp ^ a = b
136. 只出现一次的数字
思路:^
class Solution {
public int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
}
异或,表达是两个数之间的差异
1 0 -> 1
0 0 -> 0
由上可看出,相同的数异或为0,任何数与0异或得它本身
461. 汉明距离
思路:^
两者进行异或,统计得到结果中1的位数
693. 交替位二进制数
给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。
思路:>>
判断相邻两位是否一样
这里可以用滑窗思想,固定滑窗为两位,如果窗口中的数为00,或11,则返回false
00 -> 0,11-> 3
n & 3 // 如101010 & 11 ——> 101010 & 000011 高位补0
任何数与0都为0,这样就能判断n的最后两位是不是相同的了
//情况1:最后两位为00,00 & 11 = 0 return false
//情况2:最后两位为11,11 & 11 = 3 return false
//情况3: 最后两位为01, 01 & 11 = 1
//情况4: 最后两位为10, 10 & 11 = 2
代码
public boolean hasAlternatingBits(int n) {
while(n != 0){
if((n & 3) == 0 || (n & 3) == 3){
return false;
}
n >>= 1;
}
return true;
}
官方:
这个是真的绝
直接错位,两者异或
101010 >> 1 = 010101
101010 ^ 010101 = 0
class Solution {
public boolean hasAlternatingBits(int n) {
int a = n ^ (n >> 1);
return (a & (a + 1)) == 0;
}
}
1863. 找出所有子集的异或总和再求和🤒
解法不止一个,这里只讲位运算
思路:
有n个元素的数组,每一个元素都有两种状态:取或不取
那么根据状态的不同,一共会出现2ⁿ种情况,即数组有2ⁿ个子集
我们用一个n位的二进制串来表示每个元素的状态,0表示不取,1表示取
该二进制的范围为00…0 ~ 11…1,对应的十进制范围为0~(2ⁿ - 1)
2ⁿ位运算为 (1 << n)
每取一个十进制数则表示一种子集
我们把该二进制数上 出现1的所对应的元素 相互之间异或
判断第
j
位是否为1:i & (1 << j) > 0
得到的结果与其他子集的结果累加,返回
class Solution {
public int subsetXORSum(int[] nums) {
int n = nums.length;
int ans;
int sum = 0;
for(int i = 0; i < (1<<n); i++){
ans = 0;
for(int j = 0; j < n ; j++){
if( (i & (1 << j)) > 0){
ans ^= nums[j];
}
}
sum += ans;
}
return sum;
}
}
371. 两整数之和🤒
给你两个整数 a
和 b
,不使用 运算符 +
和 -
,计算并返回两整数之和。
思路:
^
:相同为0,不同则为1 → 不带进位的加法
题目可转化成:不带进位的加法 + 进位
&
:只有两个都为1时,结果为1,其余情况都为0。这样,(a & b) << 1
刚好可以表示进位
PS:这里我必须吐槽一下,这递归一下真的没想明白
递归思路整理:
-
首先,特殊情况(a=0|b=0)很好返回
-
当
a
,b
两者都不为0的时候a ^ b
:不进位的加法 设为 x(a & b) << 1
:进位 设为y即
a + b = x + y
那么
x ^ y
表示不进位的加法 + 进位
的不进位加和这样递归体就出来了
-
结束条件
两者异或的结果一定是越来越大的,这样需要进的位也会越来越小,直到需要进的位为0
举个栗子加深一下理解:
11+10 = (11 ^ 10) + ((11 & 10) << 1) = 01 + 100
01 + 100 = (01 ^ 100) + (01 & 100)
其实这里已经可以看出两个数相加不需要进位了,直接异或得出结果
class Solution {
public int getSum(int a, int b) {
return b == 0 ? a : getSum(a^b, (a & b)<< 1);
}
}
左移注意防溢出
官方的是迭代版的
面试题 05.01. 插入
即把第i~j位清0,把M塞进去
思路:
两步
-
清零,0与任何数都为0,
~(1<<k)
-
塞进去,可以用或
class Solution {
public int insertBits(int N, int M, int i, int j) {
int ans = N;
for(int k = i; k <= j; k++){
ans &= ~(1<<k);
}
return ans | (M<<i);
}
}
这里感谢一下up💞,视频短小精悍,留白给我们思考,讲得也很有趣,感兴趣的朋友可以去看一看,真的很不错。