算法沉淀——位运算(leetcode真题剖析)

在这里插入图片描述

算法沉淀——位运算

  • 常用位运算总结
    • 1.基础位运算
    • 2.确定一个数中第x位是0还是1
    • 3.将一个数的第x位改成1
    • 4.将一个数的第x位改成0
    • 5.位图
    • 6.提取一个数最右边的1
    • 7.删掉一个数最右边的1
    • 8.异或运算
    • 9.基础例题
  • 力扣题目讲解
    • 01.面试题 01.01. 判定字符是否唯一
    • 02.丢失的数字
    • 03.两整数之和
    • 04.只出现一次的数字 II
    • 05.面试题 17.19. 消失的两个数字

常用位运算总结

1.基础位运算

  1. 按位与(&):对两个二进制数的对应位进行与运算,结果中的每一位都是两个数对应位上的位与操作的结果。

    int result = a & b;
    
  2. 按位或(|):对两个二进制数的对应位进行或运算,结果中的每一位都是两个数对应位上的位或操作的结果。

    int result = a | b;
    
  3. 按位异或(^):对两个二进制数的对应位进行异或运算,结果中的每一位都是两个数对应位上的位异或操作的结果。

    int result = a ^ b;
    
  4. 按位取反(~):对一个二进制数的每一位取反,即将0变为1,将1变为0。

    int result = ~a;
    
  5. 左移(<<):将一个二进制数的所有位向左移动指定的位数,右侧用0填充。

    int result = a << 2;  // 将a的二进制表示向左移动2位
    
  6. 右移(>>): 将一个二进制数的所有位向右移动指定的位数,左侧用符号位填充(对于有符号整数),无符号整数左侧用0填充。

    int result = a >> 1;  // 将a的二进制表示向右移动1位
    

2.确定一个数中第x位是0还是1

(n>>x)&1 
  1. n >> x:右移操作符将二进制表示的整数 n 向右移动 x 位。这意味着我们把整数 n 的二进制表示向右移动 x 位。
  2. (n >> x) & 1:与操作符 & 对两个二进制数的对应位进行与运算。在这里,对 (n >> x) 的结果与二进制数 1 进行与运算。
    • 如果 (n >> x) 的二进制表示中第 x 位是1,与1进行与运算的结果是1。
    • 如果 (n >> x) 的二进制表示中第 x 位是0,与1进行与运算的结果是0。

这是一种常见的技巧,特别是在位操作中,用于提取或测试一个特定位的值。

3.将一个数的第x位改成1

n|=(1<<x)
  1. 1 << x:左移操作符将二进制数 1 向左移动 x 位。这意味着我们在二进制数 1 的基础上,将其向左移动 x 位,从而在第 x 位设置为1,其它位为0。
  2. n |= (1 << x):按位或赋值操作符 |=n 的二进制表示与 (1 << x) 的二进制表示进行按位或运算,并将结果存储回 n。这样,n 的第 x 位被设置为1,其它位保持不变。
    • 如果 n 的第 x 位原本是0,进行按位或运算后仍然为1。
    • 如果 n 的第 x 位原本是1,进行按位或运算后仍然为1。

4.将一个数的第x位改成0

n&=(~(1<<x))
  1. 1 << x:左移操作符将二进制数 1 向左移动 x 位。这意味着我们在二进制数 1 的基础上,将其向左移动 x 位,从而在第 x 位设置为1,其它位为0。
  2. ~(1 << x):按位取反操作符 ~(1 << x) 的每一位取反,即将第 x 位由1变为0,其它位由0变为1。
  3. n &= (~(1 << x)):按位与赋值操作符 &=n 的二进制表示与 (~(1 << x)) 的二进制表示进行按位与运算,并将结果存储回 n。这样,n 的第 x 位被设置为0,其它位保持不变。
    • 如果 n 的第 x 位原本是1,进行按位与运算后变为0。
    • 如果 n 的第 x 位原本是0,进行按位与运算后仍然为0。

5.位图

  1. 基本概念:位图是一个由二进制位组成的数组,其中每一位都表示一个元素的存在或缺失。通常,每个元素都与位图中的一个二进制位相对应。
  2. 表示集合:位图主要用于表示集合,其中集合中的每个元素都有一个唯一的标识符,例如整数。如果集合中的元素存在,对应位置的位被设置为1;如果元素不存在,对应位置的位被设置为0。
  3. 节省空间:相比于其他数据结构,位图在存储上更加紧凑。一个位可以表示一个元素的存在与否,因此对于包含大量小范围整数的集合,位图可以显著减少存储空间的需求。

示例:

考虑一个集合 {1, 3, 5, 7, 9},对应的位图可能如下所示:

1 0 1 0 1 0 1 0 1 0

这表示集合中的元素存在,对应位置的位为1。通过位图,我们可以方便地进行快速的集合操作和检索。

具体可以看我之前写过的一篇博客

https://blog.csdn.net/kingxzq/article/details/133775093

6.提取一个数最右边的1

n&-n
  1. 计算 -n:在计算机中,负数通常以补码形式表示。 -n 可以通过将 n 的各位取反(按位取反)然后加1得到。也就是说,-nn 的按位取反再加1。
  2. 位与操作 n & -n:将 n-n 进行按位与操作。这将保留 n 中最右边的1,而将其他位都置为0。

这个技巧的背后是,n-n 在二进制表示中只有最右边的1是相同的,其他位都是相反的。因此,按位与操作会保留这个共同的最右边的1,其他位会被置零。

例如,如果 n 的二进制表示是 1011000,那么 -n 的二进制表示是 0101000,进行按位与操作后得到 0001000,即提取了 n 中最右边的1。

7.删掉一个数最右边的1

n&(n-1)
  1. 计算 n-1:将整数 n 减去1。这相当于将 n 的最右边的1变为0,并且将该1右侧的所有位都取反。
  2. 位与操作 n & (n-1):将 nn-1 进行按位与操作。这将保留 n 中除了最右边的1之外的所有位。

通过这个操作,n 中最右边的1及其右侧的所有位都会被清零,而其他位保持不变。

例如,如果 n 的二进制表示是 1011000,那么 n-1 的二进制表示是 1010111,进行按位与操作后得到 1010000,即删除了 n 中最右边的1。

8.异或运算

  1. a ^ 0 = a:任何数与0进行异或运算的结果都是它本身。这是因为异或运算的规则是,如果两个对应位的输入相同,则输出为0,如果不同,则输出为1。因此,一个数与0进行异或,对应的位都保持不变,结果就是这个数本身。

    例如:a = 10100 = 0000,则 a ^ 0 = 1010

  2. a ^ a = 0:任何数与自己进行异或运算的结果都是0。这是因为对应位相同的情况下输出为0,而数与自己的对应位肯定相同,因此结果为0。

    例如:a = 1010,则 a ^ a = 0000

  3. a ^ b ^ c = a ^ (b ^R c):异或运算满足结合律,即无论是如何加括号,得到的结果都是相同的。这是因为异或运算的结果取决于每一位的对应关系,而不受操作数的先后顺序影响。

    例如:a = 1010b = 1100c = 0110,则 (a ^ b) ^ c = 0010a ^ (b ^ c) = 0010

9.基础例题

191. 位1的个数

338. 比特位计数

461. 汉明距离

136. 只出现一次的数字

260. 只出现一次的数字 III

力扣题目讲解

01.面试题 01.01. 判定字符是否唯一

题目链接:https://leetcode.cn/problems/is-unique-lcci/

实现一个算法,确定一个字符串 s 的所有字符是否全都不同。

示例 1:

输入: s = "leetcode"
输出: false 

示例 2:

输入: s = "abc"
输出: true

限制:

  • 0 <= len(s) <= 100
  • s[i]仅包含小写字母
  • 如果你不使用额外的数据结构,会很加分。

思路

这里我们先想到的可能是哈希的思路,再进一步优化我们可以使用数组来模拟哈希,但还不是最优的解法,因为题目要求这里是只有小写字母,因此我们可以用一个整形利用位图思想来进行每一个比特位,所以这里我们只需要确定这个整形中的x位是0还是1,以及将第x位改成1,再进行判定即可。

代码

class Solution {
public:bool isUnique(string astr) {int n = astr.size();  // 获取字符串的长度int c = 0;  // 使用一个整数c来表示出现过的字符情况for (int i = 0; i < n; ++i) {int t = 'z' - astr[i];  // 计算字符到 'z' 的距离if ((c >> t) & 1) {// 如果对应位为1,表示之前已经出现过相同的字符,返回falsereturn false;} else {// 否则将对应位设为1,表示该字符已经出现过c |= (1 << t);}}// 如果遍历完整个字符串,没有发现重复字符,返回truereturn true;}
};

解释:

  • c 是一个整数,用来表示字符串中出现过的字符情况。这里用到了位运算,通过将某一位设为1来表示某个字符是否出现过。
  • t 计算了字符到 ‘z’ 的距离,目的是在整数 c 中的相应位置标记字符是否出现过。
  • (c >> t) & 1 判断 c 中对应位是否为1,如果为1,说明之前已经有相同的字符出现,返回false。
  • c |= (1 << t) 将对应位设为1,表示该字符已经出现过。

02.丢失的数字

题目链接:https://leetcode.cn/problems/missing-number/

给定一个包含 [0, n]n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

示例 1:

输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:

输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 3:

输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:

输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。

提示:

  • n == nums.length
  • 1 <= n <= 104
  • 0 <= nums[i] <= n
  • nums 中的所有数字都 独一无二

思路

同样我们这里可以使用哈希来进行标记,但显然是需要额外的空间,这里不使用额外空间还有其他的办法,比如使用高斯求和公式求出前n项和,再减去数组内数字的和,或者使用异或运算,我们使用先和数组中的每一个数异或,再和数组长度每一位异或,最终结果就是缺少的数

代码

class Solution {
public:int missingNumber(vector<int>& nums) {int n=nums.size(),ret=0;for(auto x:nums) ret^=x;for(int i=0;i<=n;++i) ret^=i;return ret;}
};

解释:

  • ret 是用于保存异或结果的变量。
  • 第一次循环中,对数组中的所有元素进行异或运算,这样重复的元素会被抵消,最终剩下的就是缺失的数字。
  • 第二次循环中,对数组的下标和数组中的元素进行异或运算,包括了所有可能的数字,因为数组的下标范围是 [0, n],所以最终异或的结果即为缺失的数字。

03.两整数之和

题目链接:https://leetcode.cn/problems/sum-of-two-integers/

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

示例 1:

输入:a = 1, b = 2
输出:3

示例 2:

输入:a = 2, b = 3
输出:5

提示:

  • -1000 <= a, b <= 1000

思路

这里我们需要知道位运算中的一种思想,即无进位相加,详细看下面代码即可

代码

class Solution {
public:int getSum(int a, int b) {while (b) {int t = a ^ b;      // 异或运算,得到无进位的和b = (a & b) << 1;   // 与运算和左移1位,得到进位a = t;              // 更新a为无进位和,继续循环}return a;  // 当进位为0时,a即为最终的和}
};

解释:

  • a ^ b 执行异或运算,得到无进位的和。
  • (a & b) << 1 执行与运算和左移1位,得到进位。因为只有在 a 和 b 的对应位都为1时,才会产生进位。
  • a = t 更新 a 为无进位和,继续循环,直到进位为0。

04.只出现一次的数字 II

题目链接:https://leetcode.cn/problems/single-number-ii/

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。

示例 1:

输入:nums = [2,2,3,2]
输出:3

示例 2:

输入:nums = [0,1,0,1,0,1,99]
输出:99

提示:

  • 1 <= nums.length <= 3 * 104
  • -231 <= nums[i] <= 231 - 1
  • nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次

思路

  • 外层循环遍历32位整数的每一位。
  • 内层循环遍历数组中的每个元素,统计在当前位上出现的次数。
  • 对3取余,得到只出现一次的元素在当前位上的值。
  • 将当前位上的值加到结果中。

代码

class Solution {
public:int singleNumber(vector<int>& nums) {int ret = 0;for (int i = 0; i < 32; ++i) {int sum = 0;for (auto x : nums) {// 统计数组中所有元素在当前位上的和if (x >> i & 1) {sum++;}}// 对3取余,得到只出现一次的元素在当前位上的值sum %= 3;// 将当前位上的值加到结果中if (sum) {ret |= 1 << i;}}return ret;}
};

05.面试题 17.19. 消失的两个数字

题目链接:https://leetcode.cn/problems/missing-two-lcci/

给定一个数组,包含从 1 到 N 所有的整数,但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗?

以任意顺序返回这两个数字均可。

示例 1:

输入: [1]
输出: [2,3]

示例 2:

输入: [2,3]
输出: [1,4]

提示:

  • nums.length <= 30000

思路

主要是通过两次异或操作来找到缺失的两个数。

第一次异或操作:在第一次循环中,首先对数组中的所有元素和[0, n+2]范围内的所有数进行异或操作。这会得到一个结果 tmp,其中包含了两个缺失的数的异或值。由于异或的性质,重复的数都被抵消了,而缺失的两个数的位上的值则保留了下来。

找到不同的位:接着,通过循环找到 tmp 二进制表示中的一个为1的位,即找到两个缺失数的二进制表示中不同的位,用 diff 表示。

第二次异或操作:接下来,再次循环数组和[0, n+2]范围内的所有数,根据 diff 的值将它们分为两组。一组中这个位上为1,另一组中这个位上为0。这样就将问题转化为两个子问题,分别找出每组中缺失的数。

返回结果:最终返回的结果就是这两个缺失的数。将它们分别赋值给 ab

代码

class Solution {
public:vector<int> missingTwo(vector<int>& nums) {int tmp = 0;// 第一次异或,将数组中的所有元素和[0, n+2]范围内的所有数进行异或for (auto x : nums) {tmp ^= x;}for (int i = 0; i <= nums.size() + 2; ++i) {tmp ^= i;}int diff = 0;// 找到两个缺失数字的二进制表示中不同的位while (1) {if (tmp >> diff & 1) {break;}diff++;}int a = 0, b = 0;// 第二次异或,根据不同的位将数组分为两组for (auto x : nums) {if (x >> diff & 1) {a ^= x;} else {b ^= x;}}for (int i = 0; i <= nums.size() + 2; ++i) {if (i >> diff & 1) {a ^= i;} else {b ^= i;}}return {a, b};}
};

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode Python - 11.盛最多水的容器

文章目录 题目答案运行结果 题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&a…

计网day1

RTT&#xff1a;往返传播时延&#xff08;越大&#xff0c;游戏延迟&#xff09; 一.算机网络概念 网络&#xff1a;网样的东西&#xff0c;网状系统 计算机网络&#xff1a;是一个将分散得、具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功…

web 前端实现一个根据域名的判断 来显示不同的logo 和不同的标题

1.需求 有可能我做一个后台 web端 我想实现一套代码的逻辑 显示不同的公司主题logo以及内容&#xff0c;但是实际上 业务逻辑一样 2.实现 建一个store oem.ts 这个名为是 oem系统 oem.ts import { defineStore } from pinia;import { store } from /store;const oemDataLis…

并行计算导论 笔记 1

目录 并行编程平台隐式并行超标量执行/指令流水线超长指令字处理器 VLIW 内存性能系统的局限避免内存延迟的方法 并行计算平台控制结构通信模型共享地址空间平台消息传递平台对比 物理组织理想并行计算机并行计算机互联网络网络拓朴结构基于总线的网络交叉开关网络多级网络全连…

【MySQL基础】:深入探索DQL数据库查询语言的精髓(上)

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; MySQL从入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. DQL1.1 基本语法1.2 基础查询1.3 条件查询1.3 聚合函数 &#x1f324;️ 全篇…

中文GPTS使用秘籍,字节扣子Coze工作流使用全教程

大家好&#xff0c;我是斜杠君。今天和大家分享字节扣子Coze工作流创建和使用全教程&#xff0c;手把手教会你。 首先我们先来看一下如何创建一个工作流。 我们以创建这样一个工作流为例。这个工作流程的作用是&#xff1a;把用户输入的内容通过头条接口查询信息&#xff0c;把…

MySQL篇----第二十一篇

系列文章目录 文章目录 系列文章目录前言一、什么是乐观锁二、什么是悲观锁三、什么是时间戳四、什么是行级锁前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 一、…

【Tauri】(1):使用Tauri1.5版本,进行桌面应用开发,在windows,linux进行桌面GUI应用程序开发,可以打包成功,使用 vite 最方便

1&#xff0c;视频地址&#xff1a; https://www.bilibili.com/video/BV1Pz421d7s4/ 【Tauri】&#xff08;1&#xff09;&#xff1a;使用Tauri1.5版本&#xff0c;进行桌面应用开发&#xff0c;在windows&#xff0c;linux进行桌面GUI应用程序开发&#xff0c;可以打包成功&…

第四节 zookeeper集群与分布式锁

目录 1. Zookeeper集群操作 1.1 客户端操作zk集群 1.2 模拟集群异常操作 1.3 curate客户端连接zookeeper集群 2. Zookeeper实战案例 2.1 创建项目引入依赖 2.2 获取zk客户端对象 2.3 常用API 2.4 客户端向服务端写入数据流程 2.5 服务器动态上下线、客户端动态监听 2…

mysql经典4张表问题

1.数据库表结构关联图 2.问题&#xff1a; 1、查询"01"课程比"02"课程成绩高的学生的信息及课程分数3.查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩4、查询名字中含有"风"字的学生信息5、查询课程名称为"数学"&…

VMware虚拟机安装openEuler系统(二)(2024)

下面我们进行openEuler系统的一些简单配置。 1. 开启openEuler系统 在VMware Workstation Pro虚拟机软件中找到安装好的openEuler操作系统虚拟机并开启。 等待开启。 2. 安装配置 进入后选择第一个“Install openEuler 20.03-LTS”。 3. 选择系统语言 为虚拟机设置系统语言…

JVM垃圾回收机制及调优工具Arthas的使用

文章目录 1、JVM垃圾回收机制1.1 针对的内存区域1.2 怎么判断对象是否可以被回收&#xff1f;1.3 垃圾收集算法1.3.1 **标记-清除&#xff08;Mark-Sweep&#xff09;**1.3.2 复制&#xff08;Copying&#xff09;1.3.3 标记-整理&#xff08;Mark-Compact&#xff09;1.3.4 分…

python+flask+django医院预约挂号病历分时段管理系统snsj0

技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django/flask Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm . 第一&#xff0c;研究分析python技术&#xff0c…

《Linux 简易速速上手小册》第3章: 文件系统与权限(2024 最新版)

文章目录 3.1 Linux 文件系统结构3.1.1 重点基础知识3.1.2 重点案例&#xff1a;设置一个 Web 服务器3.1.3 拓展案例 1&#xff1a;日志文件分析3.1.3 拓展案例 2&#xff1a;备份用户数据 3.2 理解文件权限3.2.1 重点基础知识3.2.2 重点案例&#xff1a;共享项目文件夹3.2.3 拓…

陶陶摘苹果C++

题目&#xff1a; 代码&#xff1a; #include<iostream> using namespace std; int main(){//一、分析问题//已知&#xff1a;10 个苹果到地面的高度a[10],陶陶把手伸直的时候能够达到的最大高度height//未知&#xff1a;陶陶能够摘到的苹果的数目sum。//关系&#xff…

C++PythonC# 三语言OpenCV从零开发(8):图像平滑处理

文章目录 相关链接前言图像资源图像平滑处理图像学知识补充(重点)什么是卷积什么是图像滤波什么是方框滤波和均值滤波 代码PythonCCsharp 总结 相关链接 C&Python&Csharp in OpenCV 专栏 【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程&#xff08;附带课…

第三节 zookeeper基础应用与实战2

目录 1. Watch事件监听 1.1 一次性监听方式&#xff1a;Watcher 1.2 Curator事件监听机制 2. 事务&异步操作演示 2.1 事务演示 2.2 异步操作 3. Zookeeper权限控制 3.1 zk权限控制介绍 3.2 Scheme 权限模式 3.3 ID 授权对象 3.4 Permission权限类型 3.5 在控制台…

【大数据】Flink on Kubernetes 原理剖析

Flink on Kubernetes 原理剖析 1.基本概念2.架构图3.核心概念4.架构5.JobManager6.TaskManager7.交互8.实践8.1 Session Cluster8.2 Job Cluster 9.问题解答 Kubernetes 是 Google 开源的 容器集群管理系统&#xff0c;其提供应用部署、维护、扩展机制等功能&#xff0c;利用 K…

fast.ai 机器学习笔记(三)

机器学习 1&#xff1a;第 8 课 原文&#xff1a;medium.com/hiromi_suenaga/machine-learning-1-lesson-8-fa1a87064a53 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它&#xff0c;这些笔记将继续更…

leetcode——滑动窗口题目汇总

本章总结一下滑动窗口的解题思路&#xff1a; 在字符串中使用双指针 left 和 right 围成的一个左闭右开的区域作为一个窗口。不断将 right 向右滑动&#xff0c;直到窗口中的字符串符合条件。此时将 left 向右滑动&#xff0c;直到窗口中的字符串不符合条件&#xff0c;期间需…