算法:图解位运算以及鸽巢原理应用

2023-11-08

本篇总结位运算中常见的算法题和思路,首先总结位运算中常见的题型

实现原理

基础位运算

位运算主要包含

  1. 左移 <<
  2. 右移 >>
  3. 按位取反 ~
  4. 按位与 &
  5. 按位或 |
  6. 按位异或 ^

位图思想

1. 给定一个数n,确认它的二进制表示中第x位是0还是1

解法:(n>>x) & 1
原理:n右移x个单位,就令所求元素的二进制位移动到了第一位,再令其和1按位与,其他位都是0,只有第一位,如果n的这个位置为1,则结果为1,如果是0,则结果为0

2. 给定一个数n,将它的二进制表示的第x位改成1

解法:(1<<x) | n
原理:将1左移x个单位,就可以让二进制表示中1挪动到x的位置,再让这个左移后的1和n按位或,则就可以改变这个位置的0

3. 将一个数n的二进制表示的第x位修改成0

解法:(~(1<<x)) & n
原理:将1左移x个单位,此时除了第x位置外,其余位置都为0,再令其按位取反,此时除了第x个位置外,其余地方都为1,再让这个数和n按位与,此时其余位置不变,第x的位置就会被改变为0

找最右侧数

1. 提取一个数n二进制中最右侧的1

解法:n & -n
原理:-n的原理是按位取反再+1,将最右侧的1,左边的区域全部取反就是-n

以下图例子为例:

在这里插入图片描述

2. 去掉一个数n二进制表示中最右侧的1

解法:n & (n-1)
原理:n-1就可以把最后一个1右侧的部分全部取反,此时按位与即可

按位异或

主要应用场景是单身狗等问题

算法思路

算法思路主要就是前面的这些原理,而大部分题目就是基于上面的原理进行一些叠加,只需要把题目进行一定程度的拆分,就可以解题了

典型例题

基础位运算

位运算在前面已经学过一些,这里主要总结一些比较常见的,比较有典型的例子,较简单的不归纳

只出现一次的数字

在这里插入图片描述

class Solution 
{
public:
    int singleNumber(vector<int>& nums) 
    {
        int ret=0;
        for(auto ch : nums)
        {
            ret^=ch;
        }
        return ret;
    }
};

这里主要就是用到了按位异或的性质,按位异或的一个重要性质就是,如果一组数中每一个元素出现的此时都是偶数,只有一个是奇数,那么只需要把这个数组中所有的数据都按位异或起来,最终剩下的数就是那个数,这是由于按位异或自身的原理所产出的算法原理

只出现一次的数字III

在这里插入图片描述

class Solution 
{
public:
    vector<int> singleNumber(vector<int>& nums) 
    {
        int ret=0;
        for(auto ch :nums)
        {
            ret=ret^ch;
        }
        int lsb= ret==INT_MIN? ret:(ret&(-ret));
        int type1=0,type2=0;
        for(auto num:nums)
        {
            if(num &lsb)
            {
                type1 ^=num;
            }
            else
            {
                type2 ^=num;
            }
        }
        return {type1,type2};
    }
};

这个题本身就是基于上面题目的原理产生的,按位异或可以找到一组数中唯一出现的数,那么在这个题中却有两个数都唯一出现的,那么首先要做的就是进行分类,让这两个数归类到两个类中,以此能让其分开

那么分类的原理就是让所有数都按位异或,最终得到的就是这两个数按位异或的结果,那现在要做的就是要找到这两个数的不同点,然后把这两个数分开,具体方法就是用到了前面总结的找到不一样的1,令ret&(-ret),这样就可以找到二进制中最右侧的1,而这个1就是区分这两组数据的唯一标准

那下来做的就是把nums中的元素都和前面ret&(-ret)的结果按位异或,由于最右侧1的不同,就会天然的把数组里面的数据分成两组,而这两个不同的数据也会被分到两组中,这样找到了唯二的数据

经典题型

判断字符是否唯一

在这里插入图片描述

这里解决方法很多,可以用哈希表,排序,等等,这里采用是位图的思想,同时利用鸽巢原理进行一定程度的优化

class Solution 
{
public:
    bool isUnique(string astr) 
    {
        if(astr.size()>26)
        {
            return false;
        }

        int bitmap=0;
        for(auto ch : astr)
        {
            if((bitmap>>(ch-'a'))&1==1)
            {
                return false;
            }
            else
            {
                bitmap=bitmap | (1<<(ch-'a'));
            }
        }
        return true;
    }
};

两整数之和

在这里插入图片描述

此题需要用到按位与和按位异或的性质,按位与的性质除了相同为0,相异为1外,还有一个性质是无进位相加,因此可以利用这个性质解题,先用按位异或进行无进位相加,再求出进位是多少,再继续循环相加即可

而进位其实就是直接用的性质是两个数的二进制位都为1,则要进1,如果有一个为0则为0,其实这就是按位与的操作,但是由于进位要进1,因此这里把运算出的结果左移一个单位其实就是最终的结果,那么依据这个原理就可以求出最终的结果,当进位为0的时候循环结束

class Solution {
public:
    int getSum(int a, int b) 
    {
        while(b!=0)
        {
            int x=a^b;
            int carry=(a&b)<<1;
            a=x;
            b=carry;
        }
        return a;
    }
};

只出现一次的数字II

在这里插入图片描述

对于这个题首先可以采用哈希表的方法解决,但更好的方法是使用位运算,需要一定的观察

这里采用的是比特位计数的方法,原理就是通过观察这些数,找到每一个单独的比特位拥有的独特的规律,那对于这个题来说,规律就是对于数组nums,它当中每一个数的某一个比特位相加,最终相加的结果%3得到的就是这唯独一个数据的比特位的值,下图来解释

在这里插入图片描述

这里是举了具体的例子,如果把它抽象成字母来代表其中的数据,其实也是一样的,因为数字要不然是三个一组,要不然一个一组,而三个一组的数据最终都能被三整除,最后唯独不同的就是一个一组的数据

利用这个原理,就能很轻松的把原来的ret的每一个比特位都得到,得到每一个比特位最终这个数就是我们要求的数据

class Solution 
{
public:
    int singleNumber(vector<int>& nums) 
    {
        int ret=0;
        for(int i=0;i<32;i++)
        {
            int sum=0;
            for(auto ch:nums)
            {
                sum+=(ch>>i) & 1;
            }
            sum%=3;
            ret = ret | (sum<<i);
        }
        return ret;
    }
};

消失的两个数字

在这里插入图片描述

本题实现原理其实就是前面两个题的结合,分别是消失的数字只出现一次的数字III,但这个题的解法还有很多种,其中一种是借助鸽巢原理进行排序解决,也是一种思维较为巧妙的一种解法,可以借鉴学习

下面展示的是位运算的传统解法,鸽巢原理后续进行补充

class Solution 
{
public:
    vector<int> missingTwo(vector<int>& nums) 
    {
        int n=nums.size();
        // 找到两个数按位异或的结果
        int ret=0;
        for(int i=1;i<=n+2;i++)
        {
            ret ^= i;
        }
        for(auto ch : nums)
        {
            ret ^= ch;
        }
        // ret就是两个数按位异或的结果
        // 现在把这两个数分开找到即
        int lsb=ret&(-ret);    // 找到不同点
        int type1=0,type2=0;
        for(int i=1;i<=n+2;i++)
        {
            if(i & lsb)
            {
                type1 ^= i;
            }
            else
            {
                type2 ^= i;
            }
        }
        for(auto p : nums)
        {
            if(p & lsb)
            {
                type1 ^= p;
            }
            else
            {
                type2 ^= p;
            }
        }
        return {type1 , type2};
    }
};

鸽巢原理

鸽巢原理的概念就是,如果有n+1个鸽子飞进了n个鸽巢中,那么必定有鸽巢中至少飞进了2只鸽子

其实在前面的题目中,也有部分题可以使用鸽巢原理进行解决,例如在哈希表的使用中就经常可以用到鸽巢原理进行一定程度的优化,例如判断一个字符串中每个字符只出现一次,那么此时,如果字符串的长度大于26,那么说明这里必定会不是我们所要找的

类似的题目还有很多,但是对于上面的题目一个很简单的方法就是借助鸽巢原理进行排序来解决题目

依据这个原理,可以实现一个时间复杂度只有O(N)的排序,但是这个排序有一定的条件,条件就是要排序的数组必须是从1到n中所有数,并且每个数只出现一次,依据这个原理就可以解决问题

void sort(vector<int>& nums)
{  //6,1,2,5,-1,-1
	for (int i = 0; i < nums.size(); i++) 
	{
		while (nums[i] != -1 && nums[i] != i + 1)
		{
			swap(nums[i], nums[nums[i] - 1]);
		}
	}
}

int main()
{
	vector<int> v{ 6,1,2,5,-1,-1 };
	sort(v);
	return 0;
}

总结

位运算在面试的场景中有考察,也算是一种比较重要的算法,是应该多多练习的,而在练习的过程中要清楚位运算的基本解题原理,掌握了基本解题原理很多题目就是在基本的解题原理上进行的延伸,那么此时再进行解题就简单很多了

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法:图解位运算以及鸽巢原理应用 的相关文章

  • 更新面板工作速度非常慢

    我正在编写一个用户可以注册的应用程序 注册时 可以选择多个选项 并根据这些注册字段可见或不可见以及是否必需 我想出了一个想法 所有字段都将位于 updatePanel 中 当用户更改注册选项时 我将在服务器端设置这些字段的可见性 它可以工作
  • Exit() 时是否调用基本对象析构函数?

    我意识到这个问题已经出现过几次 但我试图获得上述问题的明确答案 但我不断遇到相互矛盾的信息 我需要知道的是 当我使用 exit 时 基本类对象是否被破坏 我知道需要删除动态内存 但我的意思更像是 include
  • Grpc - 将消息从一个客户端发送到连接到同一服务器的另一个客户端

    是否可以将消息从一个客户端发送到连接到同一服务器的另一个客户端 我想将数据从一个客户端发送到服务器然后发送到特定客户端 我想我需要获取客户端 ID 但我不知道如何获取此 ID 以及如何从服务器将此消息发送到该客户端 我这里有一个样本 这是一
  • 转换 const void*

    我有一个函数返回一个const void 我想用它的信息作为char 我可以将它投射为 C 风格的罚款 char variable但是当我尝试使用reinterpret cast like reinterpret cast
  • 现代 C++ 编译器是否能够在某些情况下避免调用 const 函数两次?

    例如 如果我有以下代码 class SomeDataProcessor public bool calc const SomeData d1 const SomeData d2 const private Some non mutable
  • 未找到 Boost 库,但编译正常

    我正在尝试在 C 中使用 boost 的文件系统 使用时看起来编译没问题 c c Analyse c o Analyse o g W Wall L usr local lib lboost filesystem lboost system
  • 从复选框列表中选择循环生成的复选框中的一个复选框

    抱歉我的英语不好 在我的 ASP NET 网站上 我从 SQL 表导入软件列表 看起来像这样 但实际上要长得多 Microsoft Application Error Reporting br br Microsoft Applicatio
  • 当事件button.click发生时,如何获取按钮名称/标签?

    我以编程方式制作按钮并将它们添加到堆栈面板中 以便每次用户导航到页面时按钮都会发生变化 我正在尝试做这样的事情 当我单击创建的按钮时 它将获取按钮的标签并转到正确的页面 但是 我无法使用 RoutedEventHandler 访问按钮元素
  • 不同 C++ 文件中的相同类名

    如果两个 C 文件具有相同名称的类的不同定义 那么当它们被编译和链接时 即使没有警告也会抛出一些东西 例如 a cc class Student public std string foo return A void foo a Stude
  • 将二变量 std::function 转换为单变量 std::function

    我有一个函数 它获取两个值 x 和 y 并返回结果 std function lt double double double gt mult double x double y return x y 现在我想得到一个常量 y 的单变量函数
  • 如何在 C# 中创建异步方法?

    我读过的每一篇博客文章都会告诉您如何在 C 中使用异步方法 但由于某些奇怪的原因 从未解释如何构建您自己的异步方法来使用 所以我现在有这段代码使用我的方法 private async void button1 Click object se
  • Oauth2中如何同时撤销RefreshToken和使AccessToken失效

    我正在使用 Owin Oauth2 授权和资源服务器相同 开发单页面应用程序 AngularJS Net MVC Json Rest API 的身份验证流程 我选择了 Bearer Token 路由而不是传统的 cookie session
  • 比较:接口方法、虚方法、抽象方法

    它们各自的优点和缺点是什么 接口方法 虚拟方法 抽象方法 什么时候应该选择什么 做出这一决定时应牢记哪些要点 虚拟和抽象几乎是一样的 虚方法在基类中有一个实现 可以选择重写 而抽象方法则没有 并且must在子类中被覆盖 否则它们是相同的 在
  • 使动态创建的链接标签在 Winforms 中可点击

    我正在制作一个程序 允许用户单击由动态链接标签创建的公司名称 在我想知道如何做到这一点之前 我从未在 C 中使用过链接标签 可为特定用户生成的业务数量各不相同 因此每个用户的链接标签数量并不相同 然后我想捕获业务 ID 以进行 Json 调
  • EntityFramework 6.0.0.0 读取数据,但不插入

    我创建了一个基于服务的数据库 folderName gt Add New Item gt Data gt Service based Database文件到 WPF 应用程序中 然后我用过Database First方法并创建了Person
  • 没有“对 *this”功能的右值引用的解决方法

    我有一个围绕可移动对象的代理容器类 并希望代理能够隐式生成对底层对象的右值引用 但仅当代理本身被移动时 我相信我将能够按照提案 n2439 实施此行为 将移动语义扩展到 this http www open std org jtc1 sc2
  • 无法将字符串文字分配给装箱的 std::string 向量

    这是我的类型系统的简化版本 include
  • 为什么空循环使用如此多的处理器时间?

    如果我的代码中有一个空的 while 循环 例如 while true 它将把处理器的使用率提高到大约 25 但是 如果我执行以下操作 while true Sleep 1 它只会使用大约1 那么这是为什么呢 更新 感谢所有精彩的回复 但我
  • 是否允许全局静态标识符以单个 _ 开头?

    换句话说 可能static 文件范围 全局变量恰好以一个下划线开头 而不会产生与 C 实现发生名称冲突的可能性 https www gnu org software libc manual html node Reserved Names
  • 如何在 C 中将 char 连接到 char* ?

    我怎样才能前置char c to char myChar 我有c值为 A and myChar值为 LL 我怎样才能前置c to myChar使 ALL 这应该有效 include

随机推荐

  • 关于Directly Mapping Texels to Pixels的例子

    原文 http msdn microsoft com en us library bb219690 28v vs 85 29 aspx 是关于在direct3d9中 对于屏幕空间中 将贴图映射到像素的问题 以下是pixel shader源代
  • c语言在线编译网页版,c语言在线编译器(c语言网页版在线编译器)

    不好意 我想要的是下载 点 问题没说清楚 sorry dev c 选择什么样的编译器对我学习C语言来说重要么 在线等大神指点 不要复制 学习C语言的话 VC基本上就差不多了 小巧 方便 启动快 而VS是大软件 启动时有点慢 GCC是linu
  • c++学习笔记3_函数模板的使用并实现自己定义的队列

    实验要求 熟悉C 目录 1 函数模板 Function Templates 类模板 Class Template 模板特化 实验部分 sy3 h sy3 cpp main cpp 运行结果 1 函数模板 Function Templates
  • 深度学习------tensorflow2.0:RNN单词预测,句子预测,股票预测

    1 单词预测 import tensorflow as tf from tensorflow keras layers import Dense Activation from tensorflow keras models import
  • 03C++核心编程——黑马程序员

    C 核心编程 本阶段主要针对C 面向对象编程技术做详细讲解 探讨C 中的核心和精髓 1 内存分区模型 C 程序在执行时 将内存大方向划分为4个区域 代码区 存放函数体的二进制代码 由操作系统进行管理的 全局区 存放全局变量和静态变量以及常量
  • python自带的解释器和编辑器叫什么_02-Python解释器和编辑器介绍

    Python解释器和编辑器介绍 解释器 python 这个解释器 是用C语言开发的 也叫 CPython 在命令行下运行 python 就是启动 CPython解释器 CPython 是使用最广的 Python解释器 教程的所有代码也都在
  • SQL的多表查询(笛卡尔积原理)

    MySQL的多表查询 笛卡尔积原理 先确定数据要用到哪些表 将多个表先通过笛卡尔积变成一个表 然后去除不符合逻辑的数据 根据两个表的关系去掉 最后当做是一个虚拟表一样来加上条件即可 注意 列名最好使用表别名来区别 笛卡尔积 Demo 左 右
  • 2023年自然语言处理与信息检索国际会议(ECNLPIR 2023)

    会议简介 Brief Introduction 2023年自然语言处理与信息检索国际会议 ECNLPIR 2023 会议时间 2023年9月22日 24日 召开地点 中国杭州 大会官网 ECNLPIR 2023 2023 Eurasian
  • 【C++】随机数rand( ) 和 随机数引擎

    rand 基本 使用随机数时 经常见到的是C标准库提供的函数rand 这个函数会生成一个0到RAND MAX 32767 之间的一个整形数 分布 为了得到一个给定范围内的随机数 通常会对生成的随机数取余 rand n rand n m m
  • 【电路设计】单节锂电池使用

    前言 最近在研究如何利用单节锂电池给3 3V单片机供电 找到两个比较好的教程 单节锂电池如何转3 3V 升压还是降压 锂电池接了保护板 就可以用五伏电压直接充电了吗 其中上面提到的LDO 这里有一个型号 ME6209 MP2155应用示例
  • nacos启动报错Fail to init node, please see the logs to find the reason.

    启动程序路径不能有中文名
  • 数据库SQLserver期末复习重点汇总

    数据库的三级模式结构 外模式 gt 概念模式 gt 内模式 模式也称概念模式或逻辑模式 是对数据库中全部数据的逻辑结构和特征的描述 是所有用户的公共数据试图 内模式也称存储模式或物理模式 是对数据物理结构和存储方式的藐视 是数据在数据库内部
  • ES6语法说明

    一 ES6语法说明 1 let 变量声明 let a b c let d 1 f 一 g let 不能重复声明 let start liu let start yuan 错误的 已经声明过的变量名 不存在变量提升 关键字let 不能先使用
  • 5.1 综合案例- 将温湿度数据发送到云端(2.2版本接口有更新)

    综合案例 将温湿度数据发送到云端 案例说明 功能实现 1 物联网平台开发 2 设备端开发 2 代码 3 测试效果 案例说明 温湿度传感器测量当前温湿度 将实时温湿度信息上传云端 从而实现云端的监管 传感器使用详见3 11 haas506 2
  • centos7安装配置hadoop-3.2.2(单机安装、伪分布式安装)

    前言 看着官网的教程还是有坑的 so总结了一下 一 环境准备 centos7 hadoop3 2 2 jdk1 8 yum install rsync y ssh 最小化安装的系统中已有ssh 不用安装 二 开始安装 1 首先安装rsync
  • 华为OD机试 - 数大雁(Python)

    题目描述 一群大雁往南飞 给定一个字符串记录地面上的游客听到的大雁叫声 请给出叫声最少由几只大雁发出 具体的 1 大雁发出的完整叫声为 quack 因为有多只大雁同一时间嘎嘎作响 所以字符串中可能会混合多个 quack 2 大雁会依次完整发
  • OSPF详解(HCIP)

    学习目标 1 了解OSPF基本特性 2 了解OSPF邻接关系建立流程 3 了解OSPF报文 4 了解1类到7类LSA 5 OSPF矢量图画法 6 OSPF不规则区域解决方法 7 OSPF网络类型 8 OSPF特殊区域特性 一 OSPF基本特
  • JAVA 记录内网服务通过外网服务获取文件流

    公司项目遇到 对接第三方接口时需要根据链接获取网络文件保存到我们自己的服务器 但是本服务无法访问外网 只能通过请求另一个服务去访问外网 故准备由外网服务获取网络文件并将文件流推送回内网服务进行保存 外网服务接口 RestController
  • 华为OD机试 C++【代表团坐车】

    题目 一场大会上 有好几个代表团同时到达 接待处的问题是 只有一辆车去接 而这车的座位是有限的 你的任务是帮助接待员算出 有多少种方法可以让这车的座位恰好坐满 不多也不少 限制条件 每个代表团的人数都不会超过车的总座位数 每个代表团的人数和
  • 算法:图解位运算以及鸽巢原理应用

    文章目录 实现原理 基础位运算 位图思想 找最右侧数 按位异或 算法思路 典型例题 基础位运算 只出现一次的数字 只出现一次的数字III 经典题型 判断字符是否唯一 两整数之和 只出现一次的数字II 消失的两个数字 鸽巢原理 总结 本篇总结