位运算

数组中只有一个数出现次数少于m次,其他数都出现了m次,求出现次数少于m次的数

解题思路

  1. 把所有数当作一个巨大的二进制,那么出现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;
}

数字范围按位与

解题思路

  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
}

反转二进制位

解题思路

  1. 位运算分治的思想,先让两个一组进行交换,再让四个一组进行交换,一直到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的个数

解题思路

  1. 可以使用lowbit算法求出来每一次最右边的1,来每次减去直到等于0,来统计1的个数
  2. 可以直接使用位运算来统计1的个数具体的思路是把每一位当做一个计数器来统计1的个数,还是分组统计和上一道题的分组思想是一样的。
  3. 我们可以用分治的思想继续来看待这个问题,如果是长度为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,因为商的值变小了
}

总结

位运算有非常多的技巧,这些技巧有的可以非常巧妙的解决问题。位运算也比其他的运算符要快,使用位运算可以提高我们的效率,以及对于位运算实现的四则运算是重重之重。

posted @ 2023-12-21 23:02  lwj1239  阅读(44)  评论(0)    收藏  举报