位操作集锦

位操作集锦

  • 异或运算
    • 两两交换
    • 数据签名
    • 检测两个数是否拥有不同的符号,即一个正数,一个负数
    • 寻找只出现一次的一个数字1
    • 寻找只出现两次的一个数字
    • 寻找只出现一次的一个数字2
    • 寻找只出现一次的两个数字
  • 与和位移运算
    • 判断奇偶数
    • 二进制数中1的个数
    • 二进制数中最右边1的位置
  • 其他小技巧
    • 整数加1
    • 求平均数
    • 不用加减乘除做加法
    • 设置第k位的值

异或运算

异或计算规则:

  • 0 ^ 0 = 0
  • 1 ^ 1 = 0
  • 0 ^ 1 = 1 ^ 0 = 1

即 异或不等为1,相等为0

两两交换

当接触编程时,两两交换的代码模版如下:

int tmp = a;
a = b;
b = tmp;

使用异或的实现:

a = a ^ b;
b = a ^ b; //(a ^ b) ^ b = a
a = a ^ b; //(a ^ b) ^ a = b

感觉就是一种炫技,很少用。

数据签名

对原始数据按字节签名,签名值 = 对数据按字节异或。这种数据签名太简单用的较少。

检测两个数是否拥有不同的符号,即一个正数,一个负数

分析:正数符号位位0,负数符号位为1,两者异或时符号位为1
两者用于不同的符号: (num1 ^ num2) < 0

寻找只出现一次的一个数字1

  • 问题:一个数组序列,除一个数字出现一次外,其他数字都出现两次,找到这个出现一次的数字。
  • 解决方案:将数组中的所有数字异或,由于相同数字异或为0,所以最终结果就是只出现一次的数字。
int find_once_num(vector<int>& nums){
	int result = 0;
	for(int num: nums){	
		result ^= num;
	}
	return result;
}

寻找只出现两次的一个数字

  • 问题:一个数组序列,除一个数字出现两次外,其他数字都出现一次,找到这个出现两次的数字。
  • 解决思路:本质上将目标出现的次数转换为奇数次,其他非目标元素出现的次数转换为偶数次
    假设知道数组序列对应的不重复数字序列,将这些数字序列拼接到数组序列中,这样出现一次的数字在新序列中就出现两次,出现两次的数字在新序列中就出现三次,从而异或后的结果就是要查找只出现两次的数字。
int find_twice_num(vector<int>& nums){
	int result = 0;
	//假设nums对应的不重复的数字序列为[1,nums.size()]
	for(int idx = 1; idx <= nums.size(); ++idx){
		result ^= idx;
	}
	for(int num: nums){	
		result ^= num;
	}
	return result;
}

寻找只出现一次的一个数字2

  • 问题描述:一个数组序列,除一个数字出现一次外,其他数字都出现三次,找到这个出现一次的数字。
  • 分析:由于目标数字和其他数字都是奇数次,没法将目标数字变成奇数次,其他数字变成偶数次,这样就没法使用异或的思路了。这里除了使用hash统计数字外,还可以从另一个角度入手:位。
    一个数字出现三次,那么某位为1时,一定该位出现会出现三次。这样每一位对3取余后的二进制位就是目标数字。
    寻找只出现一次的数字
int find_once_num(vector<int>& nums){
	int result = 0;
	int length = sizeof(int) * 8;
	for(int idx = 0; idx < length; ++idx) {
		int sum = 0;
		for(auto num: nums){
			sum += ((num >> idx) & 1);
		}
		if(sum % 3 == 1){
			result += (1 << idx);
		}
	}
	return result;
}

寻找只出现一次的两个数字

  • 问题描述:一个数组序列,除两个数字出现一次外,其他数字都出现两次,找到这两个出现一次的数字。
  • 分析:该问题不能直接套用 寻找只出现一次的一个数字1 问题的解决方案,但是可以将其转换为 寻找只出现一次的一个数字1 问题。最好是能将原数组序列拆分为两个数组序列,每个数组序列中包含一个出现一次的数字,这样就可以在该序列中使用异或来找到出现一次的数字。那么如何拆分呢?
    原数组序列进行异或后,等到的结果 = 出现一次的两个数字的异或,且该结果肯定不为0,即该结果的二进制中至少有一位为1,对应的出现一次的两个数字在该位一个为0,一个为1。因此可以使用该位来拆分原数组序列
pair<int,int> find_once_num(vector<int>& nums){
	int result = 0;
	//对原始序列求值
	for(auto num: nums){
		result ^= num;
	}
	//找到最右侧1的位置
	int idx = 0;
	while((result & 1) == 0){
		result >>= 1;
		idx++;
	}
	int num1 = 0, num2 = 0;
	for(auto num: nums){
		//基于该位置进行分类
		if((num >> idx) & 1){
			num1 ^= num;
		}else{
			num2 ^= num;
		}
	}
	return make_pair(num1,num2);
}

与和位移运算

与计算规则:

  • 0 & 0 = 0
  • 1 & 1 = 1
  • 0 & 1 = 1 & 0 = 0

位移计算规则:

  • 左移<<:右边补0,逻辑左移和算术左移一样。
  • 右移>>:逻辑右移左边补0,算术右移左边补符号位。
    在C/C++中,对于无符号数,可以认为是逻辑左移和逻辑右移。对于有符号数,可以认为是算术左移和算术右移。
#include <iostream>
using namespace std;

int main()
{
	int num = 0x80000001;
	//有符号数是算术右移:0x80000001,0xc0000000
	cout << hex << num << "," << (num >> 1) << endl;
	unsigned int unum = 0x80000001;
	//无符号数是逻辑右移:0x80000001,0x40000000
	cout << hex << unum << "," << (unum >> 1) << endl;
   return 0;
}

判断奇偶数

分析:整数的二进制表示为 a n ∗ 2 n + a n − 1 ∗ 2 n − 1 + . . . + a 2 ∗ 2 2 + a 1 ∗ 2 1 + a 0 ∗ 2 0 a_n*2^n + a_{n-1}*2^{n-1} + ... +a_2 * 2^2+a_1 * 2^1 + a_0*2^0 an2n+an12n1+...+a222+a121+a020,其中 a i = 0 ∣ 1 a_i = 0|1 ai=0∣1。由表达式可知,当 a 0 = 0 a_0 = 0 a0=0时,该表达式 a n ∗ 2 n + a n − 1 ∗ 2 n − 1 + . . . + a 2 ∗ 2 2 + a 1 ∗ 2 1 = 2 ∗ ( a n ∗ 2 n − 1 + a n − 1 ∗ 2 n − 2 + . . . + a 2 ∗ 2 1 + a 1 ∗ 2 0 ) a_n*2^n + a_{n-1}*2^{n-1} + ... +a_2 * 2^2+a_1 * 2^1 = 2 * (a_{n}*2^{n-1} + a_{n-1}*2^{n-2} + ... +a_2 * 2^1+a_1 * 2^0 ) an2n+an12n1+...+a222+a121=2(an2n1+an12n2+...+a221+a120)一定为偶数。

  • 偶数:(num & 1) == 0
  • 奇数:(num & 1) == 1

二进制数中1的个数

  • 问题:给定一个无符号整数,求它的二进制表示中 1 的个数
  • 解决方案1:n&1
    通过不停的右移,统计最后一位是否为1,时间复杂度为O(32)
int getNum(unsigned int num) {
	int cnt = num & 1;
    while ((num >>= 1) != 0) {
        cnt += num & 1;
    } 
    return cnt;
}
  • 解决方案2:n & (n-1)
    n & (n - 1) 可以将最后一位置0,比如n = 5(101),n - 1 = 4(100),n & (n - 1) = 4(100),从而置0的次数就是1出现的次数,时间复杂度为1出现的次数。
int getNum(unsigned int num) {
	int cnt = 0;
    while (num != 0) {
        cnt++;
        num &= (num-1)
    } 
    return cnt;
}

注:n & (n - 1) 还可以用于判断数值n是否是2的整数幂,当 n & (n - 1) == 0时,说明n是2的整数幂。

  • 解决方案3:大牛方案,二分法
    采用二分法的思想,先计算每两位中 1 的个数,再计算每四位、每八位、每十六位,最后把两个十六位的个数相加。
int getNum(unsigned int num) {
    //计算每两位中1的个数:最多2个1,需用2位bit
    num = (num & 0x55555555) + ((num >> 1) & 0x55555555);
    //计算每四位中1的个数:最多4个1,需用3位bit
    num = (num & 0x33333333) + ((num >> 2) & 0x33333333);
    //计算每八位中1的个数:最多8个1,需用4位bit
    num = (num + (num >> 4)) & 0x0f0f0f0f;
    //计算每十六位中1的个数:最多16个1,需用5位bit
    num = num + (num >> 8);
    //计算每三十二位中1的个数:最多32个1,需用6位bit
    num = num + (num >> 16);
    return num & 0x3f;
}

在处理过程中,首先将相邻位进行分组,然后对每分组进行处理:统一处理成低位相加,高位置零,利用二进制加法求和。
假设字节按位标号1,2,3,4,5,6,7,8,按两位分组为(1,2),(3,4),(5,6),(7,8),每个分组最多2个1,占用2bit;按四位分组,则(1,2,3,4),(5,6,7,8),每个分组最多4个1,占用3bit。

对于数值a=10010111,按两位分组(1,2),(3,4),(5,6),(7,8),即a=(10)(01)(01)(11),每个分组内数值之和即为1的个数。
为了统计每个分组内1的个数,需要将1,3,5,7位右移一位,这样就可以利用二进制加法和2,4,6,8位上的数值相加,从而完成两两分组的统计。为了避免原1,3,5,7位的数值干扰,需要将其置零,使用0x5555 = 01010101可以将1,3,5,7位置0,即
第2,4,6,8位对应的值:a1 = 10010111 & 0x5555 = 00010101
第1,3,5,7位对应的值:a2 = (10010111 >> 1) & 0x5555 = 01001011 & 0x5555 = 01000001
a1 + a2 = 01010110,即每个分组中1的个数分别对应1、1、1、2。

二进制数中最右边1的位置

问题:给定一个无符号整数,求它的二进制表示中最右边1 的位置

int getBitSite(unsigned int num){
	if(num == 0){
		return 0;
	}
	int site = 1;
	while((num&1) == 0){
		num >>= 1;
		site++;
	}
	return site;
}

扩展:如何只保留最右边的1,即除最右边的1保留外,其他位置的1置0?本质上就是清除最右边1左边的所有1。

  • 方法1:n ^ (n & (n - 1)),n & (n - 1) 除最右边的1所在位置值为0外,其他位的值跟n一致,因此两者异或后就是结果
  • 方法2:n & -n,负数采用补码的形式表示,即 -n = ~n + 1,这样-n在最右边1的左边跟n相反,在最右边1的右边跟n相同,两者与运算就是结果。比如n = 100100,则-n = -100100 = 011011 + 1 = 011100。

其他小技巧

整数加1

在计算机中,负数是采用补码表示的,补码 = 反码 + 1,即 -x = ~x + 1,因此有 x + 1 = - ~x

求平均数

常规计算方法: x + y 2 \frac {x+y} {2} 2x+y,在数学世界中,这种计算方法并没有问题,但在计算机世界中,这种写法就有问题。当x和y比较大时,会因计算机的存储溢出而导致结果不符预期。 x + y 2 = a + b 2 \frac {x+y} {2} = a + \frac {b} { 2} 2x+y=a+2b

  • 优化方案1:当a = x, b = y - x,则有 x + y − x 2 x + \frac {y - x} {2} x+2yx,该方案避免求和溢出
  • 优化方案2:(x & y) + ((x ^ y) >> 1)
    按照二进制表示 x + y 2 = ( x n + y n ) ∗ 2 n + ( x n − 1 + y n − 1 ) ∗ 2 n − 1 + . . . + ( x 1 + y 1 ) ∗ 2 1 + ( x 0 + y 0 ) ∗ 2 0 2 = ( x n + y n ) 2 ∗ 2 n + ( x n − 1 + y n − 1 ) 2 ∗ 2 n − 1 + . . . + ( x 1 + y 1 ) 2 ∗ 2 1 + ( x 0 + y 0 ) 2 ∗ 2 0 , 其中 x i , y i 分别对应 x 和 y 的二进制中对应位的值 \frac {x+y} {2} = \frac {(x_n + y_n) * 2 ^n + (x_{n-1} + y_{n-1}) * 2 ^{n-1} + ... + (x_1 + y_1) * 2 ^1 + (x_0 + y_0) * 2 ^0} {2} = \frac {(x_n + y_n)} {2} * 2 ^n + \frac {(x_{n-1} + y_{n-1})} {2}* 2 ^{n-1} + ... + \frac {(x_1 + y_1)} {2} * 2 ^1 + \frac {(x_0 + y_0)} {2} * 2 ^0,其中x_i,y_i分别对应x和y的二进制中对应位的值 2x+y=2(xn+yn)2n+(xn1+yn1)2n1+...+(x1+y1)21+(x0+y0)20=2(xn+yn)2n+2(xn1+yn1)2n1+...+2(x1+y1)21+2(x0+y0)20,其中xi,yi分别对应xy的二进制中对应位的值,因此对上面的序列进行分类:
    x i + y i = 0 ∣ 2 x_i+y_i = 0|2 xi+yi=0∣2组成序列:x & y
    x i + y i = 1 x_i+y_i = 1 xi+yi=1组成序列:(x ^ y) >> 1

不用加减乘除做加法

int add(int a, int b){
	//保留不用进位的二进制数
	int num1 = a ^ b;
	//找到需要进位的二进制数
	int num2 = a & b;
	num2 = num2 << 1;
	//循环处理,一直到没有进位为止
	if(num2 == 0){
		return num1;
	}
	//使用进位后的数值 和 不需要进位的数值 相加
	return add(num1, num2);
}

设置第k位的值

  • 将第k位值置为1:n = n | (1 << (k - 1))
  • 将第k位值置为0: n = n & ~(1 << (k - 1))
  • 检查第k位值是否为1: (n & (1 << (k - 1))) != 0
  • 切换第k位的值(即0变为1,1变为0):n = n ^ (1 << (k - 1))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/23869.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MFC 给对话框添加图片背景

在windows开发当中做界面的主要技术之一就是使用MFC&#xff0c;通常我们看到的QQ,360,暴风影音这些漂亮的界面都可以用MFC来实现。今天我们来说一下如何用MFC美化对话框&#xff0c;默认情况下&#xff0c;对话框的背景如下&#xff1a; 那么&#xff0c;我们如何将它的背景变…

C++服务器框架开发3——协程与线程的简单理解/并发与并行

该专栏记录了在学习一个开发项目的过程中遇到的疑惑和问题。 其教学视频见&#xff1a;[C高级教程]从零开始开发服务器框架(sylar) 上一篇&#xff1a;C服务器框架开发2——头文件memory/typedef C服务器框架开发3——协程与线程的简单理解/并发与并行 目前进度协程与线程的简…

json-server的基本使用

1、mock是什么&#xff1f; mockjs 作用&#xff1a;生成随机数据&#xff0c;拦截 Ajax 请求 目的&#xff1a;很多时候前端开发页面的过程中&#xff0c;后端的接口并没有写好&#xff0c;这个时候需要前端自己定义接口及接口的返回数据的结构体&#xff0c;这个时候就需要…

ReactRouterDom-v5v6用法与异同

本文作者系360奇舞团前端开发工程师 简介&#xff1a; React Router Dom是React.js中用于实现路由功能的常用库。在React应用中&#xff0c;路由可以帮助我们管理页面之间的导航和状态&#xff0c;并实现动态加载组件。本文将深入探讨React Router Dom的两个主要版本&#xff1…

【微电网】含风、光、储联合发电的微电网优化调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Jupyter程序安装和使用指南【操作示例】

Jupyter Notebook(简称Jupyter)是一个交互式编辑器&#xff0c;它支持运行40多种编程语言&#xff0c;便于创建和共享文档。Jupyter本质上是一个Web应用程序&#xff0c;与其他编辑器相比&#xff0c;它具有小巧、灵活、支持实时代码、方便图表展示等优点。下面分别为大家演示如…

辅助生成: 低延迟文本生成的新方向

大型语言模型如今风靡一时&#xff0c;许多公司投入大量资源来扩展它们规模并解锁新功能。然而&#xff0c;作为注意力持续时间不断缩短的人类&#xff0c;我们并不喜欢大模型缓慢的响应时间。由于延迟对于良好的用户体验至关重要&#xff0c;人们通常使用较小的模型来完成任务…

EnjoyVIID部署

1、下载 git clone https://gitee.com/tsingeye/EnjoyVIID.git 2、导入数据库 创建表enjoyviid 导入数据库(修改数据库文件里的编码) EnjoyVIID/sql/tsingeye-viid.sql 3、修改配置 vim EnjoyVIID/tsingeye-admin/src/main/resources/application-dev.yml 修改数据库连接、re…

接口测试--apipost接口断言详解

在做接口测试的时候&#xff0c;会对接口进行断言&#xff0c;一个完整的接口测试&#xff0c;包括&#xff1a;请求->获取响应正文->断言。 一、apipost如何进行断言 apipost的断言设置实在后执行脚本中进行编写的。apipost本身提供了11中断言&#xff1a; apt.asser…

Linux-0.11 kernel目录进程管理asm.s详解

Linux-0.11 kernel目录进程管理asm.s详解 模块简介 该模块和CPU异常处理相关&#xff0c;在代码结构上asm.s和traps.c强相关。 CPU探测到异常时&#xff0c;主要分为两种处理方式&#xff0c;一种是有错误码&#xff0c;另一种是没有错误码&#xff0c;对应的方法就是error_c…

URP自定义屏幕后处理

回到目录 大家好&#xff0c;我是阿赵。这次来说一下URP渲染管线里面怎样使用后处理效果&#xff0c;还有怎样去自定义后处理效果。 一、使用URP自带的后处理效果 要使用URP自带的后处理效果&#xff0c;方法很简单&#xff0c;和Unity内置渲染管线的PostProcessing后处理很…

任务7 课程信息管理系统

系列文章 任务7 课程信息管理系统 已知课程的信息包括&#xff1a;课程编号&#xff0c;课程名称&#xff0c;课程性质&#xff08;必修、选修&#xff09;&#xff0c;课时&#xff0c;学分&#xff0c;考核方式&#xff08;考试、考查课&#xff09;&#xff0c;开课学期&a…

Ubuntu22.04安装MySQL8

在 Ubuntu 22.04 上安装 MySQL 8&#xff0c;可以按照以下步骤进行&#xff1a; 安装MySQL需要在root用户下 sudo su -更新软件包列表&#xff1a; sudo apt update安装 MySQL 8&#xff1a; sudo apt install mysql-server安装过程中会提示设置 MySQL root 用户的密码。 确认…

(学习日记)AD学习 #4

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

波奇学C++:模板和STL

什么是模板&#xff1f;为什么我们需要模板&#xff1f; 先假设一个场景&#xff0c;我们要编写一个函数交换a,b两个数的值 void swap(int& a,int& b) {int cmpa;ab;ba; } swap函数可以帮我们交换两个int型的值&#xff0c;那如果要交换的类型是float&#xff0c;do…

【linux解压和打包文件】

TOC 打包成zip文件 指令 zip zip -r -q -o html.zip html/ -r 参数表示递归打包包含子目录的全部内容&#xff0c;-q 参数表示为安静模式&#xff0c;即不向屏幕输出信息&#xff0c;-o 表示输出文件&#xff0c;需在其后紧跟打包输出文件名。解压zip文件 1.unzip -q …

【HMS Core】【ML Kit】活体检测FAQ合集

【问题描述1】 使用示例代码集成活体检测SDK时&#xff0c;报错state code -7001 【解决方案】 使用示例代码前请详细阅读示例工程中的“README”文件。您需要完成以下操作后才可以运行示例代码。 在AppGallery Connect网站下载自己应用的“agconnect-services.json”文件&a…

服务(第三十二篇)nginx做缓存服务器

nginx作为缓存服务配置语法 1、proxy_cache_path 配置语法&#xff08;即缓存路径配置语法&#xff09; Syntax&#xff1a;proxy_cache_path path [levelslevels] [use_temp_pathon|off] keys_zonename:size [inactivetime] [max_sizesize] [manager_filesnumber] [manager_s…

深度学习常用名词解析

深度学习&#xff1a; 英文DL(Deep Learning),指多层的人工神经网络和训练它的方法。一层大量的神经网络会把大量的矩阵数字作为输入&#xff0c;通过非线性激活方法获取权重&#xff0c;再产生另一个数据集和作为输出。 Epoch&#xff1a; 在模型训练的时候含义是训练集中的…

减肥瘦身自律APP软件开发功能有哪些?

减肥瘦身是很多女人一生都在奋斗的目标&#xff0c;如果找不对方法&#xff0c;减肥效果事倍功半还可能会反弹&#xff0c;所以越来越多的人推崇健康科学的减肥理念&#xff0c;把瘦身的重心转移到饮食和运动管理上&#xff0c;于是市场上出现了减肥瘦身自律类的APP软件&#x…