目录
一、相关操作符
1. 移位操作符
① >>(右移)
右移分为两种:算数右移和逻辑右移
算数右移:左边补原来的符号位,右边丢弃(vs通常是算数右移)
逻辑右移:左边补0,右边丢弃
(无符号类型左边补零)
② <<(左移)
左移规则:左边丢弃, 右边补零
注意:不能移动负数位,这是标准未定义的!
2. 位操作符
① &(按位与)
对于两个数 &,有0则为0,两个1才为1
运用:用&计算一个数2进制中1的个数
//计算n的二进制中1的个数 int count = 0; while (n) { n &= (n - 1); count++; }
具体原理大家可以自己画图分析😁
② | (按位或)
对于两个数 |,有1则为1,两个0才为0
③ ^(按位异或)
对于两个数 ^ ,相同为0, 相异为1
二、题目详解
1) 二进制中1的个数
leetcode传送➡️https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/?favorite=xb9nqhhg
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)。
示例 1:
输入:n = 11 (控制台输入 00000000000000000000000000001011) 输出:3 解释:输入的二进制串00000000000000000000000000001011 中,共有三位为 '1'。
题目分析
这是一道位运算入门题目,首先我们知道整数在内存中以补码(二进制)的形式存储,直接遍历二进制中每一位判断是0是1即可;
如何取到二进制中的每一位呢? 我们想到用1依次&(按位与)这个整数的每一位,1的二进制补码除了第一位是1其余全是零,而&“有零则零”,如果1与整数对应的位置是0则两者按位与的结果肯定是0,如果对应的位置是1那么结果则是非0;
最后则需要考虑如何将1的二进制补码中1的位置移动,直接用移位操作符“>>”或“<<”即可
图解
整体代码
int hammingWeight(uint32_t n) {
int sum = 0;
for(int i = 31;i>=0;i--)
{
if(n&(1u<<i)) //1u 与 (unsigned int) 1 表示含义相同
sum++;
}
return sum;
}
2)不用加减乘除做加法
leetcode传送➡️https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/?favorite=xb9nqhhg
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1 输出: 2
题目分析
不能用加减乘除,那么自然就会想到用位运算了,那么用什么操作符呢?
我们知道二进制中两个1相加会向前进一位,0和1相加不需要进位该位得1,那我们就需要将进位与不进位的分开算
&是当两个为1的时候才为1,正符合我们需要的两个1相加,再将按位与的结果向左移一位即可,那么向左移一位后还有可能再次进位啊?所以我们需要一个循环,直到没有进位为止;
接下来就是处理没有进位的:用^按位异或即可——相同为0相异为1
接下来我们用画图的方式进一步理解
图解
整体代码
int add(int a, int b){
int c = 0;
while(b != 0){
c = (unsigned int)(a & b)<<1; //举例:00111...1111 与 00000...0001
a ^= b;
b = c;
}
return a;
}
3)数组中数字出现的次数
leetcode传送➡️https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/?favorite=xb9nqhhg
一个整型数组
nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。示例 1:
输入:nums = [4,1,4,6] 输出:[1,6] 或 [6,1]
题目分析
这道题看起来是非常简单的,当你做下去时会发现相同的很容易用^抵消,但是那两个不相同的怎么也分离不开😤
所以这道题的重点是将两个不相同的数组分离
很容易想到将数组的所有数遍历异或得到一个只有两个不相同的两个数异或的结果(假设这个数为n);
由“相同为0, 相异为1”得,异或得结果中总有至少一位为1,我们找到这一位再以此为划分界限将数组中的所有数异或0,这两个分别划分异或的结果就是这道题的答案了
(有人会问,将不同的两个数分开了,那么数组中的其他数不会有影响嘛?答案是不会的,因为相同的两个数与n2&,得到的结果是一样的,会分到同一边去)
核心代码
int* singleNumbers(int* nums, int numsSize, int* returnSize){
*returnSize = 2;
int*ret = (int*)malloc(sizeof(int)*2);
ret[0] = ret[1] = 0;
int n = 0;
//将所有数异或
for(int i = 0; i < numsSize; i++){
n ^= nums[i];
}
//找到两个数不同的一位(异或为1)
int n2 = 1;
while( (n2 & n) == 0){ //&“有0则0”,遇到两个1则说明找到n有1的一位
n2 = n2 << 1;
}
//以n2为界限将数组中的所有数分别与0异或得到两个答案
for(int i = 0; i < numsSize; i++){
if((nums[i] & n2) == 0){
ret[0] ^= nums[i];
}
else{
ret[1] ^= nums[i];
}
}
return ret;
}
4)数组中数字出现的次数 II
leetcode传送➡️https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/?favorite=xb9nqhhg
在一个数组
nums
中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。示例 1:
输入:nums = [3,4,3,3] 输出:4
题目分析
分析题目,与之前的两个数不同,这里只有一个不同的数,其余数是三个三个的共同出现;
我们知道二进制位中只有1和0,将一个数分成三十二个位来看,将所有数加起来那么一个位的总和只有3n+1,3n,1三种情况将这一位%3,得到的就只有0和1了,因为其余数都是三个三个的出现,那么%3之后得到的1肯定是那个单独出现的数在该位是1,相反如果为0,那么在该位一定为0;
图解
整体代码
int singleNumber(int* nums, int numsSize){
int result = 0;
int bit = 0;
//将三十二位分别计算
for(int i = 0; i < 32; i++) {
bit = 0;
//将数组中所有数的第i位相加
for(int j = 0; j < numsSize; j++) {
bit += (nums[j] >> i) & 1;
}
//将总和%3加在结果的第i位,表示那位不同的数在该位的值
result += (bit%3)<<i;
}
return result;
}
拓展
以这样的解法思维,可以解在一个数组 nums
中除一个数字只出现一次之外,其他数字都出现了N次的题目,只需将总和%N即可