位运算的高频算法题

关卡名

位运算的高频算法题

我会了✔️

内容

1.理解位运算如何统计1的个数的

✔️

2.理解位运算如何实现加法

✔️

3.理解递归乘法是如何实现的

✔️

1 位移的妙用 

位移操作是一个很重要的问题,可以统计数字中1的个数,在很多高性能软件中也大量应用,我们看几个高频题目。

1.1 位1的个数

LeetCode191 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数。
拓展问题:16进制时怎么统计0000 00de

示例1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。示例2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。

首先我们可以根据题目要求直接计算,题目给定的 n 是 32 位二进制表示下的一个整数,计算位 1 的个数的最简单的方法是遍历 n 的二进制表示的每一位,判断每一位是否为 1,同时进行计数。
那问题就是如何通过位运算来识别到1,例如:00001001001000100001100010001001,首先我们注意到要识别到最低位的1,可以这么做:

00001001001000100001100010001001

& 00000000000000000000000000000001

= 00000000000000000000000000000001

也就说将原始数字和1进行&运算就能知道最低位是不是1了,那其他位置怎么算呢?
我们可以有两种思路,让1不断左移或者将原始数据不断右移。例如将原始数据右移就是:

00000100100100010000110001000100

& 00000000000000000000000000000001

= 00000000000000000000000000000000

很明显此时就可以判断出第二位是0,然后继续将原始数据右移就可以依次判断出每个位置是不是1了。因此是不是1,计算一下(n>>i) & 1就可以了,所以代码顺理成章: 

public int hammingWeight(int n) {int count = 0;for (int i = 0; i < 32; i++) {count += (n >> i) & 1;}return count;
}

这个题也可以通过将1左移来实现的,该问题作为一个作业题,请你改造上面的代码来实现。
上面的代码写出来,这个题基本就达标了,但这还不是最经典的解法,我们继续分析:
按位与运算有一个性质:对于整数 n,计算n & (n−1) 的结果为将 n 的二进制表示的最后一个 1 变成 0。利用这条性质,令 n=n & (n−1),则 n 的二进制表示中的 1 的数量减少一个。重复该操作,直到 n 的二进制表示中的全部数位都变成 0,则操作次数即为 n 的位 1 的个数。什么意思呢?我们继续看上面的例子:

n: 00000100100100010000110001000100

n-1: 00000100100100010000110001000011

n&(n-1)= 00000100100100010000110001000000

可以看到此时n&(n-1)的结果比n少了一个1,此时我们令n=n&(n-1),继续执行上述操作:

n: 00000100100100010000110001000000

n-1: 00000100100100010000110000111111

n&(n-1)= 00000100100100010000110000000000

可以看到此时n&(n-1)的结果比上一个n又少了一个1,所以我们令n=n&(n-1),循环执行上述操作,我们统计一下循环执行的次数就能得到结果了。
那循环该什么时候停下呢?很显然当n变成0的时候,否则说明数据里面还有1,可以继续循环。所以当且仅当 n=0 时,n 的二进制表示中的全部数位都是 0,代码也很好写了:

public int hammingWeight(int n) {int count=0;while(n!=0){n=n&(n-1);count++;}return count;
}

上面两种解法,第一种的循环次数取决于原始数字的位数,而第二种的取决于1的个数,效率自然要高出不少,使用n = n & (n - 1)计算是位运算的一个经典技巧,该结论可以完美用到下面的题目中: 

1.2 比特位计数 

LeetCode338.给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

示例1:

输入:n = 2

输出:[0,1,1]

解释:0到n有 0 1 2 三个数字,每个数字含有1的个数分别为0 1 1 个,如下:

0 --> 0

1 --> 1

2 --> 10

示例2:

输入:n = 5

0 1 2 3 4 5

101

输出:[0,1,1,2,1,2]

解释:0到n有 0 1 2 3 4 5 六个数字,每个数字含有1的个数分别为0,1,1,2,1,2个,如下:

0 --> 0

1 --> 1

2 --> 10

3 --> 11

4 --> 100

5 --> 101

最直观的方法是对从 0 到 num 的每个数直接计算"一比特数"。每个int 型的数都可以用 32 位二进制数表示,只要遍历其二进制表示的每一位即可得到1 的数目。 

public int[] countBits(int num) {int bits=new int[num+1];for(int i=0;i<=num;i++){for(int j=0;j<32;j++){bits[i]+=(i>>j)&1;}}return bits;
}

利用位运算的技巧,可以提升计算速度。按位与运算(&)的一个性质是:对于任意整数 x,令 x=x&(x−1),该运算将 x 的二进制表示的最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成0,则操作次数即为 x 的「一比特数」。

public int[] countBits(int num) {int[] bits = new int[num + 1];for (int i = 0; i <= num; i++) {bits[i] = countOnes(i);}return bits;}public int countOnes(int x) {int ones = 0;while (x > 0) {x &= (x - 1);ones++;}return ones;
}

 有没有发现比特位计数和位1的个数计算规则完全一样? 这就是为什么我们说研究清楚一道题,可以干掉一大票的题目。

1.3 颠倒无符号整数

LeetCode190 .颠倒给定的 32 位无符号整数的二进制位。 提示:输入是一个长度为32的二进制字符串。

示例1:

输入:n = 00000010100101000001111010011100

输出:964176192 (00111001011110000010100101000000)

解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,

因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。

示例2:

输入:n = 11111111111111111111111111111101

输出:3221225471 (10111111111111111111111111111111)

解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,

因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。

首先这里说是无符号位,那不必考虑正负的问题,最高位的1也不表示符号位,这就省掉很多麻烦。
我们注意到对于 n 的二进制表示的从低到高第 i 位,在颠倒之后变成第 31-i 位( 0≤i<32),所以可以从低到高遍历 n 的二进制表示的每一位,将其放到其在颠倒之后的位置,最后相加即可。
看个例子,为了方便我们使用比较短的16位演示:

原始数据:1001 1111 0000 0110(低位)

第一步:获得n的最低位0,然后将其右移16-1=15位,得到:

reversed: 0*** **** **** ****

n右移一位: 0100 1111 1000 0011

第二步:继续获得上面n的最低位1,然后将其右移15-1=14位,并与reversed相加得到:

reversed:01** **** **** ****

n右移一位:0010 0111 1100 0001

继续,一直到n全部变成0: 

理解之后,实现就比较容易了。由于 Java不存在无符号类型,所有的表示整数的类型都是有符号类型,因此需要区分算术右移和逻辑右移,在Java 中,算术右移的符号是 >>,逻辑右移的符号是 >>>。

public int reverseBits(int n) {int reversed = 0, power = 31;while (n != 0) {reversed += (n & 1) << power;n >>>= 1;power--;}return reversed;
}

 本题的解法还有很多,例如还有一种分块的思想, n 的二进制表示有 32 位,可以将 n 的二进制表示分成较小的块,然后将每个块的二进制位分别颠倒,最后将每个块的结果合并得到最终结果。这分治的策略,将 n 的 32 位二进制表示分成两个 16 位的块,并将这两个块颠倒;然后对每个 16 位的块重复上述操作,直到达到 1 位的块。为了方便看清楚,我们用字母代替01,如下图所示。
具体做法是:
下面的代码中,每一行分别将 n 分成16 位、8 位、4 位、2 位、1 位的块,即把每个块分成两个较小的块,并将分成的两个较小的块颠倒。同样需要注意,使用 Java 实现时,右移运算必须使用逻辑右移。由于是固定的32位,我们不必写循环或者递归,直接写:

  reverseBits(int n) {n = (n >>> 16) | (n << 16);n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8);n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4);n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2);n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1);return n;}

 这种方法在JDK、Dubbo等源码中都能见到,特别是涉及协议解析的场景几乎都少不了位操作。积累相关的技巧,可以方便面试,也有利于阅读源码。

面试算法和工程算法

2 位实现加减乘除专题

在计算机中,位运算的效率比加减乘数效率更高,因此在高性能软件的源码中大量应用,而且计算机里各种运算本质上都是位运算。本专题我们就研究几个相关问题。

2.1 位运算实现加法

LeetCode371 给你两个整数 a 和 b ,不使用 运算符 + 和 - ,计算并返回两整数之和。

示例1:

输入:a = 1, b = 2

输出:3

既然不能使用+和-,那只能使用位运算了。我们看一下两个二进制位相加的情况:

[1] 0 + 0 = 0

[2] 0 + 1 = 1

[3] 1 + 0 = 1

[4] 1 + 1 = 0 (发生了进位,应该是10的)

两个位加的时候,我们无非就考虑两个问题:进位部分是什么,不进位部分是什么。从上面的结果可以看到,对于a和b两个数不进位部分的情况是:相同为0,不同为1,这不就是a⊕b吗?
而对于进位,我们发现只有a和b都是1的时候才会进位,而且进位只能是1,这不就是a&b=1吗?然后位数由1位变成了两位,也就是上面的[4]的样子,那怎么将1向前挪一下呢?手动移位一下就好了,也就是(a & b) << 1。所以我们得到两条结论:

  • 不进位部分:用a⊕b计算就可以了。
  • 是否进位,以及进位值使用(a & b) << 1计算就可以了。

于是,我们可以将整数 a 和 b 的和,拆分为 a 和 b 的无进位加法结果与进位结果的和,代码就是: 

public int getSum(int a, int b) {while (b != 0) {int sign = (a & b) << 1;a = a ^ b;b = sign;}return a;
}

2.2 递归乘法 

LeetCode里面试08.05,递归乘法。 写一个递归函数,不使用 * 运算符, 实现两个正整数的相乘。可以使用加号、减号、位移,但要吝啬一些。

示例1:

输入:A = 1, B = 10

输出:10

如果不让用*来计算,一种是将一个作为循环的参数,对另一个进行累加,但是这样效率太低,所以我们还是要考虑位运算。
首先,求得A和B的最小值和最大值,对其中的最小值当做乘数(为什么选最小值,因为选最小值当乘数,可以算的少),将其拆分成2的幂的和,即min = a_0 * 2^0 + a_1 * 2^1 + ... + a_i * 2^i + ...其中a_i取0或者1。其实就是用二进制的视角去看待min,比如12用二进制表示就是1100,即1000+0100。例如:
13 * 12 = 13 * (8 + 4) = 13 * 8 + 13 * 4 = (13 << 3) + (13 << 2);
上面仍然需要左移5次,存在重复计算,可以进一步简化:
假设我们需要的结果是ans,
定义临时变量:tmp=13<<2 =52计算之后,可以先让ans=52
然后tmp继续左移一次tmp=52<<1=104,此时再让ans=ans+tmp
这样只要执行三次移位和一次加法,实现代码:

public int multiply(int A, int B) {int min = Math.min(A,B);int max = Math.max(A,B);int ans = 0;for(int i=0; min!=0; i++){if((min&1)==1){ans += max;}min >>= 1;max += max;}
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

群晖NAS配置之自有服务器frp实现内网穿透

什么是frp frp 是一个专注于内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议&#xff0c;且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。今天跟大家分享一下frp实现内网穿透 为什么使用 frp &a…

基于瑞芯微rk3588+寒武纪 | 38TOPS INT8算力的AI边缘计算盒子,智能安防、智慧工地、智慧城管、智慧油站

边缘计算盒子 瑞芯微rk3588寒武纪 | 38TOPS INT8算力 ● 采用 Big-Little 大小核架构&#xff0c;搭载四核 A76四核 A55&#xff0c;CPU主频高达 2.4GHz &#xff0c;提供1MB L2 Cache 和 3MB L3 &#xff0c;Cache提供更强的 CPU 运算能力。 ● 高性能四核 Mali-G610 GPU&a…

【2024秋招】2023-9-22 金山云文档服务端开发一面

1 OS 1.1 堆和栈的区别&#xff0c;什么时候用到堆&#xff0c;什么时候用到栈呢 堆和栈是两种不同的内存分配方式&#xff0c;它们在计算机编程中有着各自的用途和特点。以下是它们之间的主要区别以及在何时使用它们&#xff1a; 1.1.1 堆&#xff08;Heap&#xff09;&…

在vue中如何书写 SSR 友好的代码

文章目录 前言服务端的响应性​组件生命周期钩子​访问平台特有 API​跨请求状态污染​激活不匹配​自定义指令​teleports​后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;vue.js &#x1f431;‍&#x1f453;博主在前端领域还有很多…

i++和++i的区别

i和i的区别 一、基本概念 两者的作用都是自增加1。 单独拿出来说的话&#xff0c;i和i&#xff0c;效果都是一样的&#xff0c;就是ii1 public static void main(String[] args) {int i 0;i;System.out.println(i);}public static void main(String[] args) {int i 0;i;Sys…

【开源】基于JAVA语言的校园电商物流云平台

项目编号&#xff1a; S 034 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S034&#xff0c;文末获取源码。} 项目编号&#xff1a;S034&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 商品数据模块2.3 快…

FH Admin Shiro反序列化漏洞复现

0x01 产品简介 FH Admin 是一款 java 快速开发平台。 0x02 漏洞概述 FH Admin CMS 存在 shiro 反序列化漏洞&#xff0c;该漏洞源于软件存在硬编码的 shiro-key&#xff0c;攻击者可利用该 key 生成恶意的序列化数据&#xff0c;在服务器上执行任意代码&#xff0c;执行系统命…

python自动化第二篇——合并ppt

简述 python合并ppt的方法有很多&#xff0c;但网上常说的python-pptx的方法&#xff0c;我用不了&#xff0c;这里我用了一个python-office的库。但又两个缺点&#xff0c;第一个生成的文档在你的用户名下的文档里&#xff0c;第二个是名字随机。 import office import os im…

华为OD机试真题-查找接口成功率最优时间段-2023年OD统一考试(C卷)

题目描述: 服务之间交换的接口成功率作为服务调用关键质量特性,某个时间段内的接口失败率使用一个数组表示,数组中每个元素都是单位时间内失败率数值,数组中的数值为0~100的整数,给定一个数值(minAverageLost)表示某个时间段内平均失败率容忍值,即平均失败率小于等于minA…

vue3-vite-ts:编写Rollup插件并使用 / 优化构建过程

一、vue3-vite-ts项目&#xff0c;编写Rollup插件并使用的意义 在使用Vue3 Vite TypeScript这种技术栈时&#xff0c;可以使用Rollup插件来优化构建过程&#xff0c;例如使用rollup-plugin-typescript2插件来编译TypeScript代码&#xff0c;使用rollup-plugin-vue插件来处理…

【开源】基于Vue+SpringBoot的康复中心管理系统

项目编号&#xff1a; S 056 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S056&#xff0c;文末获取源码。} 项目编号&#xff1a;S056&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员…

版本控制系统Git学习笔记-Git服务器

文章目录 概述一、协议1.1 本地协议1.2 HTTP协议1.3 SSH协议1.4 Git协议 二、在服务器上搭建 Git 四智武童 一月一&#xff0c;捡花衣。二月二&#xff0c;练大字。三月三&#xff0c;穿新衣。四月四&#xff0c;去考试。考一个状元郎&#xff0c;坐著马车平天下。 概述 Git访…

OpenStack-train版安装之安装Keystone(认证服务)、Glance(镜像服务)、Placement

安装Keystone&#xff08;认证服务&#xff09;、Glance&#xff08;镜像服务&#xff09;、Placement 安装Keystone&#xff08;认证服务&#xff09;安装Glance&#xff08;镜像服务&#xff09;安装Placement 安装Keystone&#xff08;认证服务&#xff09; 数据库创建、创…

WebSocket 实战:构建高效的实时应用

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

使用Spark写入数据到数据库表

项目场景&#xff1a; 使用Spark写入数据到数据库表 问题描述 Column "20231201" not found in schema Some(StructType(StructField(sdate,IntegerType,false),StructField(date_time,StringType,true),StructField(num,LongType,false),StructField(table_code,S…

数据结构详解各种算法

1、设有两个整型顺序表L1&#xff0c;L2&#xff0c;其元素值递增有序存放&#xff0c;请定义该顺序表的元素类型及表类型,设计以下自定义函数&#xff1a; &#xff08;1&#xff09;录入顺序表中所有元素的值。 &#xff08;2&#xff09;将顺序表L1&#xff0c;L2合并为到…

LeetCode 8 字符串转整数

题目描述 字符串转换整数 (atoi) 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格检查下一…

解决woocommerce产品方面遇到的小问题记录

问题1.通过自定义代码在woocommerce的任意一个产品的价格下面&#xff0c;加上一段文字 id&#xff0c;换成你自己的产品id&#xff0c;div里面的文字换成你自己的自定义文字&#xff0c;代码是加在function.php里面的哦 //添加文字产品价格下面 function insert_custom_cont…

SpringMVC利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值

在我们进行Java的Web应用开发时&#xff0c;如何写更少的代码&#xff0c;做更多的事情。如何让开发更容易上手&#xff0c;更专注于业务层面&#xff0c;不需要太关心底层的实现。这里就分享一些我平时在搭建基础框架时候的一些心得体验。 统一处理返回值 在web应用中&#x…

集成开发环境 PyCharm 的安装【侯小啾python领航班系列(二)】

集成开发环境PyCharm的安装【侯小啾python领航计划系列(二)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…