算法之位运算

常见的位运算操作:

首先先熟悉一下常见的位运算操作: 

1. 基础位运算

左移<<, 右移>>, 按位与&, 按位或|, 按位异或^, 按位取反~

注意: 异或其实是一种无进位相加. 

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

n & (1<<x) 或者 (n>>x) & 1

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

n |= (1<<x)

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

n &= ~(1<<x)

5. 位图的思想

6. 提取一个数n二进制表示中最右端的1

n & -n

7. 干掉一个数n二进制表示中最右端的1--- Brian Kernighan 算法

对于任意整数 x, 令 x = x & (x−1), 该运算将 x 的二进制表示的最后一个 1 变成 0. 

8 位运算的优先级

不确定就加括号

9 异或运算的运算律

1.交换律:a ^ b = b ^ a

2.结合律:a ^ (b ^ c) = (a ^ b) ^ c

3.自反性:a ^ a = 0,a ^ 0 = a


 练习1: 位1的个数

方法1:

可以直接循环检查给定整数 n 的 32 个二进制位的每一位是否为 1, 当检查第 i 位时, 我们可以让 n 与 1<<i进行与运算, 当且仅当 n 的第 i 位为 1 时, 运算结果不为 0.

class Solution {
public:
    int hammingWeight(uint32_t n) 
    {
        int ret = 0;
        for(int i = 0; i < 32; i++)
            if(n & (1 << i))
                ret++;
        return ret;
    }
};

方法2: 位运算的优化

利用之前总结的性质, n & (n-1) 可以将最后一个1消除, 所以利用循环每次消除n最右侧的一个1, 循环执行的次数就是1的个数:

class Solution {
public:
    int hammingWeight(uint32_t n) 
    {
        int ret = 0;
        while(n)
        {
            n &= n-1;
            ret++;
        }
        return ret;
    }
};

练习2: 比特位计数

利用上一题的函数, 依次计算n个数中位1的个数即可:

class Solution {
public:
    int hammingWeight(uint32_t n) 
    {
        int ret = 0;
        while(n)
        {
            n &= n-1;
            ret++;
        }
        return ret;
    }

    vector<int> countBits(int n) 
    {
        vector<int> ret;
        for(int i = 0;i<=n;i++)
        {
            ret.push_back(hammingWeight(i));
        }
        return ret;
    }
};


练习3: 汉明距离

计算 x 和 y 之间的汉明距离,可以先计算 x⊕y , 然后统计 x⊕y 中 1 的位数.

class Solution {
public:
    int hammingDistance(int x, int y) 
    {
        int n = x^y, ret = 0;
        while(n)
        {
            n &= n-1;
            ret++;
        }
        return ret;
    }
};

练习4: 只出现一次的数字 

利用异或运算的自反性, 很容易写出: 

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

 


练习5: 只出现一次的数组3

此题和上一题不同, nums中有两个出现一次的数字, 不能直接用上一题的方法, 但是思考: 所有数异或起来的结果有没有什么特点呢?

出现两次的数字一定都两两相消了, 两个不同的数字它们一定有至少一个比特位是不同的, 也就是异或和结果一定有一位是1.

对于这一个比特位, 数组中的所有数要么这一位为0, 要么这一位为1, 用这个特性就可以把数组的数分为两组, 每组求一遍异或和, 结果就是只出现一次的那个数字, 只需要找到那个比特位为1的位置即可. 

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) 
    {
        int sum = 0;
        for(auto e: nums)
            sum ^= e;
    
        int pos = 0;//计算右边第一个1的位置
        for(int i = 0; i < 32;i++)
        {
            if(sum>>i & 1)
                pos = i;
        }
        
        int num1 = 0, num2 = 0;
        for(auto e : nums)
        {
            if(e>>pos & 1)
                num1 ^= e;
            else 
                num2 ^= e;
        }
        return {num1,num2};
    }
};

题目1: 判断字符是否为1

利用 位图 的思想, 每一个比特位代表一个字符, 一个 int 类型的变量的 32 位足够表示所有的小写字母. 比特位里面如果是 0, 表示这个字符没有出现过;比特位里面的值是 1 , 表示该字符出现过.
那么我们就可以用一个整数来充当哈希表

此外, 还可以利用鸽巢原理来做优化, 如果给定字符串长度大于26, 则一定会有一个重复字符.

class Solution {
public:
    bool isUnique(string astr) 
    {
        if(astr.length() > 26)
            return false;
        int bitmap = 0;
        for(auto c : astr)
            if(((bitmap ^= 1<<c-97) & (1<<c-97)) == 0)
                return false;
        return true;
    }
};

题目2: 丢失的数字

此题和二分查找里的 一题"点名" 很像,  但是那道题是有序排列的数组, 能找到二段性从而利用二分查找去寻找缺失的值.

此题数组中的元素无序, 找不到二段性, 可以考虑用哈希表存储, 等差数列求和求解, 也可以用位运算异或求解, 先将0-n的数字异或, 再与nums中的每个元素异或, 结果就是缺失的数字.

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

题目3: 两整数之和

最开始提到过, 异或是无进位的加法, 所以只要能找到进位, 就可以间接实现加法,  

以13 + 28 = 41为例, a=13,b=28, a^b的结果是无进位加法得到的结果, 而只有两个比特位都为1才会产生进位, 所以 a&b 就会得到产生进位的位置,  产生的进位需要加到下一位上, 所以需要(a&b)<<1, 这样才能对应进位要加到的位置上.

两者再进行异或相加, 再次计算进位的位置:

再进行一步异或相加, 发现不会再产生进位了, 那么加法就结束了, 最后一次 a^b 得到的结果就是最终加法的结果: 

class Solution {
public:
    int getSum(int a, int b) 
    {
        int sum = a ^ b;
        int carry = (a & b) << 1;
        while(jinwei)
        {
            int tmp = (sum & carry) << 1;
            sum = sum ^ jinwei;
            carry = tmp;
        }
        return sum;
    }
};

题目4: 只出现一次的数字2

 

考虑答案的第 i 个二进制位, 它可能为 0 或 1. 对于数组中非答案的元素, 每一个元素都出现了 3 次, 对应着第 i 个二进制位的 3 个 0 或 3 个 1, 无论是哪一种情况, 它们的1的数量出现的次数都是3n(n>=0), 因此: 答案第 i 个二进制位就是数组中所有元素的第 i 个二进制位 1 的次数之和以 3 的余数.

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

        return ret;
    }
};

题目5: 消失的两个数字

 本题可以转化为只出现一次的数字3, 求0~n中缺少的两个数字, 如果把1~n全部添加进数组中, 问题就转化为了:

做法和只出现一次的数组3一模一样了:

class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) 
    {
        int sum = 0, pos = 0, n = nums.size();
        //把1~n和nums里的数全部异或起来
        for(int i = 1; i <= n+2; i++)
            nums.push_back(i);
        for(auto e: nums)
            sum ^= e;
            
        //寻找最右边的1
        for(int i = 0; i < 32; i++)
        {
            if(sum>>i & 1)
            {
                pos = i;
                break;
            }
        }

        //分组异或
        int num1 = 0, num2 = 0;
        for(auto e: nums)
        {
            if(e>>pos & 1)
                num1 ^= e;
            else 
                num2 ^= e;
        }
        return {num1,num2};
    }
};

 


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

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

相关文章

消费者组大观:5种状态,1场分布式奇迹

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 消费者组大观&#xff1a;5种状态&#xff0c;1场分布式奇迹 前言EmptyDead状态处理 Dead 状态的策略&#xff1a;防范和恢复&#xff1a; PreparingRebalance处理 "PreparingRebalance" 状…

【Leetcode-102.二叉树的层序遍历】

题目详情&#xff1a; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示例…

Linux软件管理(1)

软件管理 下载 wget Linux wget是一个下载文件的工具&#xff0c;它用在命令行下。 wget工具体积小但功能完善&#xff0c;它支持断点下载功能&#xff0c;同时支持FTP和HTTP下载方式&#xff0c;支持代理服务器和设置起来方便简单。 1.语法 wget [选项]……[URL]…… 2、…

React - 实现菜单栏滚动

简介 本文将会基于react实现滚动菜单栏功能。 技术实现 实现效果 点击菜单&#xff0c;内容区域会自动滚动到对应卡片。内容区域滑动&#xff0c;指定菜单栏会被选中。 ScrollMenu.js import {useRef, useState} from "react"; import ./ScrollMenu.css;export co…

【MATLAB源码-第165期】基于matlab的科莫多巨蜥算法(KMA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 科莫多巨蜥算法&#xff08;Komodo Mlipir Algorithm&#xff0c;简称KMA&#xff09;是一种受到印尼科莫多岛上独特生物——科莫多巨蜥启发的创新算法。尽管这个算法的名称听起来很有趣&#xff0c;但实际上它并不是一个公认…

写论文matplotlib使用同一色系的颜色

推荐网站 Colorsinspo - All in one resource for finding everything about colors | Colorsinspo 网页直接可以复制颜色 除此之外&#xff0c;自己还试了一个色系&#xff08;可惜不理想&#xff0c;看着不均匀&#xff09;&#xff0c;先存到这里 color_name [lightgr…

数据结构从入门到精通——直接插入排序

直接插入排序 前言一、直接插入排序的基本思想&#xff1a;二、直接插入排序的实例三、直接插入排序的动图展示四、直接插入排序的具体代码test.c 前言 直接插入排序是一种简单的排序算法&#xff0c;其工作原理是逐个将待排序元素插入到已排序序列中的适当位置&#xff0c;直…

7-初识Keras:轻松完成神经网络模型搭建

声明 本文章基于哔哩哔哩付费课程《小白也能听懂的人工智能原理》。仅供学习记录、分享&#xff0c;严禁他用&#xff01;&#xff01;如有侵权&#xff0c;请联系删除 目录 一、知识引入 &#xff08;一&#xff09;矩阵和向量 1、向量 2、矩阵 &#xff08;二&#xff…

java Flink(四十三)Flink Interval Join源码解析以及简单实例

背景 之前我们在一片文章里简单介绍过Flink的多流合并算子 java Flink&#xff08;三十六&#xff09;Flink多流合并算子UNION、CONNECT、CoGroup、Join 今天我们通过Flink 1.14的源码对Flink的Interval Join进行深入的理解。 Interval Join不是两个窗口做关联&#xff0c;…

001_【基础篇】SpringBoot入门案例创建与实现

要求&#xff1a;使用 Springboot 开发一个 web 程序&#xff0c;浏览器发起请求/hello后&#xff0c;给浏览器返回字符串 hello springboot 使用 springboot 只需要引入一个起步依赖 <dependency><groupId>org.springframework.boot</groupId><artifac…

STP环路避免实验(思科)

华为设备参考&#xff1a;STP环路避免实验&#xff08;华为&#xff09; 一&#xff0c;技术简介 Spanning Tree Protocol&#xff08;STP&#xff09;&#xff0c;即生成树协议&#xff0c;是一种数据链路层协议。主要作用是防止二层环路&#xff0c;并自适应网络变化和故障…

个人网站制作 Part 9 添加发布、管理博客功能 | Web开发项目

文章目录 &#x1f469;‍&#x1f4bb; 基础Web开发练手项目系列&#xff1a;个人网站制作&#x1f680; 添加博客功能&#x1f528;使用Express和MongoDB&#x1f527;步骤 1: 创建博客模型&#x1f527;步骤 2: 创建博客路由 &#x1f528;使用前端框架&#x1f527;步骤 3:…

什么是零日攻击?

一、零日攻击的概念 零日攻击是指利用零日漏洞对系统或软件应用发动的网络攻击。 零日漏洞也称零时差漏洞&#xff0c;通常是指还没有补丁的安全漏洞。由于零日漏洞的严重级别通常较高&#xff0c;所以零日攻击往往也具有很大的破坏性。 目前&#xff0c;任何安全产品或解决方案…

OxyPlot 导出图片

在 OxyPlot 官方文档 https://oxyplot.readthedocs.io/en/latest/export/index.html 中查看 这里用到的是导出到 PNG 文件的方法&#xff0c;不过用的 NuGet 包最新版&#xff08;2.1.0&#xff09;中&#xff0c;PngExporter 中并没有 Background 属性&#xff1a; 所以如果图…

字符函数与字符串函数

前言 本次博客可以说内容最为多的一次博客&#xff0c;讲解同样很细致大家好好看看 1字符函数 在讲解字符函数时,大家得了解什么是字符吧 普通字符a b c 1 转义字符 \n 换行‘ \t’ 水平制表符\r回车 大家了解即可 在C语言中字符也可以有分类 所以我们先来看看…

软件测试经验与教训

大概在18年的时候&#xff0c;就看过《软件测试经验与教训》的纸制版&#xff0c;里面的一些观点深刻的影响了我&#xff0c;也影响了后来我对测试的思考。最近又一次快速阅读了电子版&#xff0c;还是收获满满。下面精选出10条&#xff0c;和大家分享。 一、测试人员是项目的…

testng测试类第2步

创建xml文件并编写xml文件 并学习其中的参数 1、创建 xml文件 在测试包->右键找到creat TestNG XML 创建xml文件 如果报错就可以粘贴过来 认识原始文件 这里首行是标识&#xff0c;其次是2个参数&#xff0c;name是测试套件的名称&#xff0c;谁的测试套件一般是公司名称…

JAVA实战手册-开篇总述

该专题以实战为出发点&#xff0c;总结概述了实际工作中常用的java知识点&#xff0c;掌握了这些知识点&#xff0c;日常工作开发以及面试都不在话下。 话不多说&#xff0c;直入正题&#xff0c;以下为JAVA知识点概括总结&#xff08;总计涵盖了10大类78小项&#xff09; 针对…

AcWing 727. 菱形——像拼图一样做题

题目描述] 分析&#xff1a; 利用程序根据输入的整数&#xff0c;画出由字符*构成的该整数阶的实心菱形。给出一个示例&#xff1a; n 7 n7 n7。 * * * * * * * * * * * * * * * * * * * * * * * * * 我们将采取拆解问题&#xff0c;通过四个部分的…