位运算
数组中只有一个数出现次数少于m次,其他数都出现了m次,求出现次数少于m次的数
解题思路
- 把所有数当作一个巨大的二进制,那么出现m次的数必定在它们在的位置出现了m的倍数次,而少于m次的必定没有在一些位置出现m次,所有只要不是m的倍数次的数,那么它一定属于少于出现m次的数之中。
int f(int a[], int size, int m)
{
int cnt[32] = {0};//用来统计一个数中每一位出现的次数
int ans = 0;
for (int i = 0; i < size; i++) {
for (int j = 31; j >= 0; j-- )
if ((a[i] >> j) & 1) {
cnt[j]++;//将对应的位数加一
}
}
for (int i = 0; i < 32; i++)
{
if (cnt[i] % m != 0) {//如果出现的次数不是m的倍数,代表它就是没有出现m次的数的位数之一
ans = ans | (1LL << i);//加上它
}
}
return ans;//返回最后的组合起来的数
}
int singleNumber(int* nums, int numsSize) {
return f(nums, numsSize, 3);
}
返回大于等于n的最小2的幂
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 1001
int near2power(int n);
int main(int argc, char* argv[])
{
return 0;
}
int near2power(int n)
{
if (n <= 0) {
return 1;
}
n--;//去掉最后一位1
//然后把所有n--后面的位数都变成1
n |= (u int)n >> 1;
n |= (u int)n >> 2;
n |= (u int)n >> 4;
n |= (u int)n >> 8;
n |= (u int)n >> 16;
return n + 1;
}
数字范围按位与
解题思路
- 如果right大于left,那么必定存在right - 1在区间里面那么,right-1肯定要与right按位与,那么它们按位与的结果是消掉right的最右边的1,而right减一就是去掉right最右边一个一的数,所以再按位与就是每次去掉最右边的那一个1。那么继续再和left比,一直持续下去,直到right大于等于left为止。
代码实现
int rangeBitwiseAnd(int left, int right){
while(left < right) {
right -= right & - right;//right & -right的结果是取出right的最右边的1,所有减去它就是还剩下的值
}
return right;//是right而不能是left,因为可能right = left + 1,所有left也可能消掉right的最右边的1,所以不是left
}
反转二进制位
解题思路
- 位运算分治的思想,先让两个一组进行交换,再让四个一组进行交换,一直到16一组进行交换。
代码实现
uint32_t reverseBits(uint32_t n) {
n = ((1LL * n & 0xAAAAAAAA) >> 1) | ((1LL * n & 0x55555555) << 1);//按1位分组交换
n = ((1LL * n & 0xCCCCCCCC) >> 2) | ((1LL * n & 0x33333333) << 2);//按2位分组交换
n = ((1LL * n & 0xF0F0F0F0) >> 4) | ((1LL * n & 0x0F0F0F0F) << 4);//按4位分组交换
n = ((1LL * n & 0xFF00FF00) >> 8) | ((1LL * n & 0x00FF00FF) << 8);//按8位分组交换
n = ((1LL * n & 0xFFFF0000) >> 16) | ((1LL * n & 0x0000FFFF) << 16);//按16位分组交换
return n;
}
图片原理解析

统计二进制中1的个数
解题思路
- 可以使用lowbit算法求出来每一次最右边的1,来每次减去直到等于0,来统计1的个数
- 可以直接使用位运算来统计1的个数具体的思路是把每一位当做一个计数器来统计1的个数,还是分组统计和上一道题的分组思想是一样的。
- 我们可以用分治的思想继续来看待这个问题,如果是长度为1的位数,那么它的值就代表着它有多少个一,那么长度为2的二进制怎么得到它一的个数呢,我们可以取出最左边和最右边的数相加,就等于长度为2的二进制中1的个数了。那么长度为4的二进制数,也是一样如果我们知道长度为2的子数中1的个数,那么我们就可以求出长度为4的数中1的个数,以此来递归。那么我们就可以求出长度为32位二进制中1的个数。
代码实现
int hammingWeight(uint32_t n) {
n = ((n & 0xAAAAAAAA) >> 1LL) + (n & 0x55555555);//把奇数位和偶数位取出相加,代表长度为2的中数1的个数
n = ((n & 0xCCCCCCCC) >> 2LL) + (n & 0x33333333);//长度为4的1中个数
n = ((n & 0xF0F0F0F0) >> 4LL) + (n & 0x0F0F0F0F);//长度为8的1中个数
n = ((n & 0xFF00FF00) >> 8LL) + (n & 0x00FF00FF);//长度为16中1的个数
n = ((n & 0xFFFF0000) >> 16LL) + (n & 0x0000FFFF);//长度为32中1的个数
return n;//返回长度为32中1的个数
}
位运算实现四则运算
加法
原理:把两个数的加法分为两部分,即不进位信息和进位信息,而结果为当没有进位信息时,不进位信息就是我们的答案。
例子1:(52)10 + (19)10 = (71)10

例子2:(5)10 + (7)10 = (101)2 + (111)2 = (12)10 = (1100)2

代码实现
int sum(int a, int b)
{
int ans = a;//存放无进位信息
while(b) {//只要还有进位信息,代表加法还没有完成,继续循环
ans = a ^ b;//a和b的无进位信息
b = (a & b) << 1;//a和b的进位信息
a = ans;//更新a来继续这个过程
}
return ans;
}
减法
减法可以被当作加法运算,因为减一个数等于加上这个数的相反数,而怎么用位运算求一个数的相反数呢,就是这个数取反再加一。
代码实现
int minus(int a, int b)
{
return sum(a, neg(b));//加一个数等于加这个数的相反数
}
int neg(int n)
{
return sum(~n, 1);//n的相反数等于~n + 1
}
乘法
乘法可以被拆分成多个结果相加的过程,过程就是竖式计算乘法的过程。
代码实现
int multi(int a, int b)
{
int result = 0;
while (b)//只要还有要乘的位数
{
if (b & 1)//如果要相乘
result = sum(result, a);//那么就等于它们相加,因为每次a都向左移了,所以等于a做了乘法,那么就直接相加就可以了
a <<= 1;//a每次都要左移,因为乘的数要乘以2,因为后面的数是加在前面的而后面的位数补零
b = (unsigned int)b >> 1;//一位一位看到底要不要相乘
}
return result;
}
除法
原理:把被除数分解成为2的幂次乘以除数的形式
例子1:(25) / 3 = 2 ^ 3 * 3,而25 / 3 = 8
就是把被除数拆成多个除数乘以2的幂。然后2的幂的累加和就是商。
代码实现
int div(int a, int b)
{
int ans = 0;
int x = a < 0 ? neg(a) : a;//把a转化成正数
int y = b < 0 ? neg(b) : b;//把b转化成正数
int diff = sign(a) ^ sign(b);//判断a和b的符号是不是不同的,如果不同diff是1,相同是0
for (int i = 30; i >= 0; i = minus(i, 1)) {//从30位开始判断,因为不用再看符号位了,一直到最后一位
if (x >> i >= y) {//如果x >= y * 2 ^ i次方代表商里面有2^i
ans |= 1 << i;//加上2 ^ i
x = minus(x, y << i);//x的值减去y * 2 ^ i,因为是累加的过程,x的值不变就错了。
}
}
return diff ? neg(ans) : ans;//如果符号不同返回商的相反数,否则返会商就行了
}
int divide(int a, int b)
{
if (b == 0) {
printf("被除数为0");
return -1;
}
if (a == INT_MIN && b == INT_MIN) {//如果a和b都是INT的最小值,返回1
return 1;
}
else if (a != INT_MIN && b == INT_MIN) {//如果a不是INT的最小值b是INT的最小值,返回0,因为是向下取整的。
return 0;
}
else if (a != INT_MIN && b != INT_MIN) {//如果a和b都不是INT的最小值,用div函数就可以计算商了
return div(a, b);
}
else if (a == INT_MIN && b == neg(1)) {//这个是题目要求可以根据情况自己设置
return INT_MAX;
}
a = sum(a, b > 0 ? b : neg(b));//不管怎么样让a的值不能是INT的最小值,让a的值变大
int ans = div(a, b);//因为a不是INT的最小值,所以可以直接算商了
return ans + (b > 0 ? neg(1) : 1);//如果b是大于0的,那么最后的商就要减1,因为商的值变大了。
//如果b小于0,那么商的值就要加1,因为商的值变小了
}
总结
位运算有非常多的技巧,这些技巧有的可以非常巧妙的解决问题。位运算也比其他的运算符要快,使用位运算可以提高我们的效率,以及对于位运算实现的四则运算是重重之重。

浙公网安备 33010602011771号