算法-位运算基础

news2024/12/24 7:39:48

文章目录

    • 前置知识
    • 1. 交换两个数
    • 2. 比较两个数的大小
    • 3. leetcode268 寻找缺失的数字
    • 4. leetcode136 只出现一次的数字
    • 5. leetcode260 只出现一次的数字|||
    • 6. leetcode137 只出现一次的数字||
    • 7. 2/3的幂
    • 8. 大于等于该数字的最小2的幂
    • 9. leetcode201 数字范围按位与
    • 10. 位运算中分治法举例

前置知识

本节最重要的一个算法 Brain Kernighan算法
大致内容如下 :
如何提取出来一个数的最右侧的1
n = n & (~n + 1)
因为 (~n + 1) == -n
所以该算法也可以写成
n = n & (-n)

1. 交换两个数

因为我们的异或运算遵循的交换律和结合律, 所以我们可以写出下面的这一段代码

public void swap(int[] arr, int i, int j) {
        //注意这里的 i != j
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

请注意这里面的 i != j 因为如果相等的话, 会直接把这一块区域置为0

2. 比较两个数的大小

尝试不用任何的比较操作就比较出来两个数的大小关系, 我们写出来的代码如下

/**
     * 不包含比较相关的逻辑运算符来得到两个数当中的最大值
     */
    //flip意为反转 --> 此时传入的n保证为 1 / 0 --> 如果是1, 就转化为0 , 如果是0, 就转化为1
    private int flip(int n) {
        return n ^ 1;
    }

    //获取一下某个数字的符号,如果是非负的数字就返回一个1, 如果是负数就返回0
    private int sign(int n) {
        return flip(n >>> 31);
    }

    //下面我们给出两种判断的方法 --> 第一种有可能会溢出, 第二种不会溢出
    //核心的逻辑就是设置 returnA , returnB 来规定什么时候返回a, 什么时候返回b(两个return一定是互斥的才可以)
    public int getMax1(int a, int b) {
        //c可能会溢出
        int c = a - b;
        int returnA = sign(c);
        int returnB = flip(returnA);
        return a * returnA + b * returnB;
    }

解释一下我们的代码 :
sign方法是为了获取到某一个数的符号, 如果是非负数就返回1, 如果是负数就返回0, flip方法是为了把翻转数字,保证传入的数字一定是0或1, 其实这段代码的本质逻辑就是定义了两个returnA和returnB变量, 这两个变量一定是互斥的(一个1,一个0), 在必要的条件下就返回值
举例:
比如 a == 5, b == 3, c == 2, returnA为获取一下c的符号是非负也就是1, 那么returnB就是相反的0, 所以最后return的结果就是returnA * a == a
思考 : 上述代码是不是有问题呢?
其实, 当我们的c是有可能发生溢出的, 比如a = Integer.MAX_VALUE;
b = Integer.MIN_VALUE, 显然我们的c是溢出的, 所以我们的代码是有问题的, 我们的改动如下(flip和sign方法是不变的)

//下面的这个方法是不会溢出的
    public int getMax2(int a, int b) {
        //c依然可能会溢出
        int c = a - b;

        //下面判断一下a b c的符号
        int signA = sign(a);
        int signB = sign(b);
        int signC = sign(c);

        //判断a b 符号是不是相同的
        int diffAB = signA ^ signB; //不同返回1
        int sameAB = flip(diffAB); //相同返回1

        //什么时候进行a的返回 
        //--> 1. ab不同号且a为非负
        //--> 2. ab同号(此时不可能会溢出)且signC == 1
        int returnA = diffAB * signA + sameAB * signC;
        int returnB = flip(returnA);
        return a * returnA + b * returnB;
    }

上述的代码逻辑和之前的那个其实是一致的
就是加入了一些是否越界的判断(到底什么时候returnA)

3. leetcode268 寻找缺失的数字

在这里插入图片描述

前置知识
假设一堆数字异或的结果我们记为异或和 : ans
这一堆数字的部分数字的结果我们记作 : eor1
另一部分记作 : eor2
显然有 eor1 ^ eor2 = ans
两边同时 ^ eor1
可以得到 eor2 = ans ^ eor1
有了上面的结论的铺垫, 写这个题应该是十分的容易, 代码实现如下

class Solution {
    public int missingNumber(int[] nums) {
        //说白了这个题就是异或运算的性质
        int eor = 0;
        for(int elem : nums){
            eor ^= elem;
        }
        int eorN = 0;
        for(int i = 0; i <= nums.length; ++i){
            eorN ^= i;
        }
        return eorN ^eor;
    }
}

4. leetcode136 只出现一次的数字

在这里插入图片描述

这个问题可以抽象为下面的这个问题
给你一个数组, 其中一种数字出现了奇数次, 另外的数字都出现了偶数次
比较简单好想的思路是创建一个Set集合对元素进行去重操作(略)
这里我们用异或运算来写
思考 : 由于异或运算满足交换律, 不管多少个偶数个相同数字异或的结果一定是0
所以只要异或一轮就是那个结果, 代码实现如下

class Solution {
    public int singleNumber(int[] nums) {
        int eor = 0;
        for(int element : nums){
            eor = eor ^ element;
        }
        return eor;
    }
}

5. leetcode260 只出现一次的数字|||

在这里插入图片描述

该问题与上面的问题类似, 我们把该问题抽象出来就是, 给了一组数字, 其中有两个元素出现了奇数次, 其他的所有元素出现了偶数次, 求出来两个元素
思路分析 :
先把所有的元素异或起来得到一个异或和 eor , 该异或和的结果就是那两个数字(我们简介为m,n)
也就是 m ^ n == eor , 然后通过Brain Kernighan算法, 得到了最右侧的1, 因为我们的异或运算也可以等同于无进位相加, 所以这个1必定来源于m和n的其中一个, 所以我们定义一个eorN,让这个eorN只异或该位为1的数字, 得到的eorN就是m/n的其中一个,问题得解

class Solution {
    public int[] singleNumber(int[] nums) {
        int eor = 0;
        for(int elem : nums){
            eor ^= elem;
        }
        int n = eor & (~eor + 1);
        int eorN = 0;
        for(int elem : nums){
            if((elem & n) == 0){
                eorN ^= elem;
            }
        }
        return new int[]{eorN, eor ^ eorN};
    }
}

6. leetcode137 只出现一次的数字||

在这里插入图片描述

把该问题抽象出来, 就是一组数字, 其中一个数字出现次数小于m次, 其他的所有数字都出现了m次, 求出来这个数字是多少
思路分析 :
我们通过分位操作, 同意每一位上出现的1的个数, 遍历这个数组, 如果是hash[i] % m != 0, 就证明该数字这一位是1, 问题得解
代码实现如下

class Solution {
    public int singleNumber(int[] nums) {
        //首先进行的是统计每一个数位上的1的个数
        int[] hash = new int[32];
        for(int elem : nums){
            for(int i = 0; i < 32; ++i){
                hash[i] = hash[i] + ((elem >>> i) & 1);
            }
        }

        //统计1的分位个数完毕, 开始还原数字
        int ans = 0;
        for(int i = 0; i < 32; ++i){
            if(hash[i] % 3 != 0){
                ans = ans | (1 << i);
            }
        }
        return ans;
    }
}

7. 2/3的幂

给一个数判断是不是2的幂, 这个没什么可说的, 直接用Brain Kernighan算法

class Solution {
	/**
     * 判断一个数字是不是2的幂
     * @param n
     * @return
     */
    public boolean isPowerOfTwo(int n){
        return n > 0 && (n & (~n + 1)) == n;
    }

    /**
     * 判断一个数字是不是3的幂(直接找到int范围内3的最大的幂是多少
     * @param n
     * @return
     */
    public boolean isPowerOfThree(int n){
        return n > 0 && 1162261467 % n == 0;
    }

}

8. 大于等于该数字的最小2的幂

已知n是非负数, 请返回大于等于n的最小的2的幂
思路分析 :
假如数字的二进制序列是 : …0010100110 , 那么此时大于等于该数的最小的2的幂就是最左侧1的一个位置
假如数字的二进制序列是 : …0001000000 , 那么此时大于等于该数的最小的2的幂就是该数本身
假设有一种方案可以把 最左侧的1后面的所有二进制位都刷成1 , 那么+1以后的结果就是答案
为什么要先进行 n-- , 是为了满足n正好是2的幂的情况
下面的几行代码的作用就是将最左侧的1的右面的二进制位全部刷成1
代码实现如下

class Solution{
	public int nearTwoPower(int n){
        if(n <= 1){
            return 1;
        }
        n--;
        n = n | (n >>> 1);
        n = n | (n >>> 2);
        n = n | (n >>> 4);
        n = n | (n >>> 8);
        n = n | (n >>> 16);
        return n + 1;
    }
}

9. leetcode201 数字范围按位与

在这里插入图片描述

暴力算法肯定是不可取的, 所以我们要采取更加好的算法
指定区间按位与(肯定是不能暴力解法)
思考 : 假设我们的left == right, 那么我们最终的与的结果就是left / right
如果 left != right, 那么此时我让right减小一点, 也就是(right - 1) & left
那么从 m ~ right的所有数字与起来的结果是不变的都是m, 因为前缀不变, 后缀全是0
循环下去, 直到 left >= right (中间的过程用的是Brain Kernighan算法)
代码实现如下

class Solution{
	public int rangeBitswiseAnd(int left,int right){
        while (left < right) {
            right = right - (right & (~right + 1));
        }
        return right;
    }
}

10. 位运算中分治法举例

我们给出来两道题, 第一道题就是逆序二进制数位, 第二道题就是汉明距离(统计二进制中的1的个数) , leetcode的题号 190 . 461
这个自己慢慢悟吧, 下面给出来代码实现(让我自己懂的)

class Solution{
	/**
     * 翻转二进制的状态
     * 比如一个数的二进制位是 : 0001101101010010 --reverse--> 010010101011000
     * 分析 : 正常的解法就是定义一个ans, 看到哪一位上有i你就或上去一个1就行了, 这里我们不在多说, 有点简单
     *       我们重点说一下位运算分治的思路(从两个一组翻转 --> 四个一组 --> 八个一组.....)
     *       假如有一个序列  a b c d e f g h
     *       我们翻转的过程: 1. b a d c f e h g (1v1翻转)
     *                    2. d c b a h g f e (2v2翻转)
     *                    3. h g f e d c b a (4v4翻转)
     *       下面推广到代码上 :
     *                   1. a b c d e f g h & 0 1 0 1 0 1 0 1  ==> 0 b 0 d 0 f 0 h (1)
     *                      a b c d e f g h & 1 0 1 0 1 0 1 0 ==>  a 0 c 0 e 0 g 0 (2)
     *                      (1) << 1 | (2) >>> 1  ==> b a d c f e h g
     *       下面的过程以此类推
     * @param n
     * @return
     */
    public int reverseBits(int n){
        n = ((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >>> 1);
        n = ((n & 0x33333333) << 2) | ((n & 0xcccccccc) >>> 2);
        n = ((n & 0x0f0f0f0f) << 4) | ((n & 0xf0f0f0f0) >>> 4);
        n = ((n & 0x00ff00ff) << 8) | ((n & 0xff00ff00) >>> 8);
        n = ((n & 0x0000ffff) << 16) | ((n & 0xffff0000) >>> 16);
        return n;
    }


    /**
     * 计算一个数字的二进制位中有几个1
     * 还是用的位运算分治的方法
     *   思路分析 : 假如数字的二进制位是  1 0 1 1 0 1 0 1
     *
     * @param n
     * @return
     */
    public int cntOnes(int n){
        n = (n & 0x55555555) + ((n >>> 1) & 0x55555555);
        n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
        n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f);
        n = (n & 0x00ff00ff) + ((n >>> 8) & 0x00ff00ff);
        n = (n & 0x0000ffff) + ((n >>> 16) & 0x0000ffff);
        return n;
    }
}

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

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

相关文章

JAVA笔试题目

1.标识符的使用 2.类名和java文件名的关系 3.java数据类型关系 4.循环体的考验 答案选择C&#xff0c;D的话需要在do前面加上loop:表示跳出当前循环体。 5.三元运算符的类型运算 6.局部变量的使用 这里需要注意的是c表示当前行代码还是使用原来的数值&#xff0c;下一行代码才…

fiddler抓包工具

概念 概念&#xff1a; Fiddler是一个http协议调试代理工具&#xff0c;它能够记录并检查所有你的电脑和互联网之间的http通讯。 http&#xff1a;不加密&#xff0c;端口为80 https&#xff1a;加密&#xff0c;端口为443 原理&#xff1a; 其实就在访问服务器时&#xff0…

005-GeoGebra基础篇-GeoGebra的矩形

上一篇关于点的介绍已经触及到了诸多GeoGebra的基础操作&#xff0c;这一篇我们根据画矩形&#xff0c;继续探索GeoGebra。 目录 一、最粗暴的方式绘制矩形1. 使用“Polygon”工具直接绘制2. 注意看代数列表3. 关于矩形和线段 二、用点和线段绘制矩形&#xff08;1&#xff09;…

CJSON库

目录 一、介绍 1、JSON是什么 2、为什么使用CJSON 3、JSON格式 二、使用CJSON构造JSON 1、创建对象 2、添加字段 3、转换格式 4、释放对象 三、使用CJSON解析JSON 1、解析数据 2、 获取字段 3、释放对象 一、介绍 1、JSON是什么 JSON是什么呢&#xff1f;JSON全称…

【Python3的内置函数和使用方法】

目录 Python 特点 Python 中文编码 Python 变量类型 Python列表 Python 元组 元组是另一个数据类型&#xff0c;类似于 List&#xff08;列表&#xff09; Python 字典 Python数据类型转换 Python 运算符 Python算术运算符 Python比较运算符 Python赋值运算符 Pyt…

clion ctrl+左键只能跳转到虚函数的声明处

右击函数 -> GOTO -> Definition 这样不够便捷&#xff0c;但是我没有找到更好的办法 可能是因为该函数是虚函数的重写&#xff0c;clion 无法识别出该函数是虚函数的哪个重写版&#xff0c;只能跳转到唯一的虚函数位置

FlowUs息流打造AI赋能下的知识库,信息深度挖掘与智能创作!FlowUs让你的数据资产更有价值

在AI时代的大潮中&#xff0c;FlowUs息流笔记类数据库凭借其强大的数据资产管理能力&#xff0c;正以前所未有的方式重塑着知识工作者的学习、研究与协作模式。当深厚的数据资产遇上AI的智能助力&#xff0c;无论是学术论文的撰写&#xff0c;还是高效提炼多人会议的核心观点&a…

Summaries

摘要是网格项&#xff0c;它利用聚合函数来显示有关所显示数据的摘要信息&#xff1a;总记录计数、最小值等。 GridControl-Grid View Summary Types 汇总 汇总总数&#xff08;GridSummaryItem&#xff09;是根据所有数据网格记录计算的&#xff0c;并显示在视图页脚中。启…

一文讲透大模型 Qwen2 的训练与推理

通义千问最近问鼎开源模型Top 1 &#xff0c;今天我来分享一下Qwen2系列模型&#xff0c;Qwen2系列模型是Qwen1.5系列模型的重大升级。包括了&#xff1a; 5个尺⼨的预训练和指令微调模型, 包括Qwen2-0.5B、Qwen2-1.5B、Qwen2-7B、Qwen2-57B-A14B以及Qwen2-72B&#xff1b; 在…

如何解决java程序CPU负载过高问题

1、介绍 在生产环境中&#xff0c;有时会遇到cpu占用过高且一直下不去的场景。这种情况可能会导致服务器宕机&#xff0c;进而中断对外服务&#xff0c;也会影响硬件寿命。 2、原因 1、Java代码存在因递归不当等原因导致的死循环的问题&#xff0c;推荐有条件的循环&#xf…

一个项目学习IOS开发---创建一个IOS开发项目

前提&#xff1a; 由于IOS开发只能在MacOS上开发&#xff0c;所以黑苹果或者购买一台MacBook Pro是每个IOS开发者必备的技能或者工具之一 Swift开发工具一般使用MacOS提供的Xcode开发工具 首先Mac Store下载Xcode工具 安装之后打开会提醒你安装IOS的SDK&#xff0c;安装好之…

统计信号处理基础 习题解答11-12

题目 证明 的MAP估计量为 其中是一个的矢量, 是一个可逆的p*p的矩阵。也就是说&#xff0c;MAP估计量对可逆的线性变换是可以变换的。 解答 已知的联合概率密度 且&#xff1a; 现在知道&#xff1a; 那么为了获得变换后的MAP&#xff0c;首先需要根据求出 根据概率密度变换…

【图解大数据技术】Hadoop、HDFS、MapReduce、Yarn

【图解大数据技术】Hadoop、HDFS、MapReduce、Yarn HadoopHDFSHDFS架构写文件流程读文件流程 MapReduceMapReduce简介MapReduce整体流程 Yarn Hadoop Hadoop是Apache开源的分布式大数据存储与计算框架&#xff0c;由HDFS、MapReduce、Yarn三部分组成。广义上的Hadoop其实是指H…

Linux中彩色打印

看之前关注下公众号呗 第1部分&#xff1a;引言 1.1 Python在文本处理中的重要性 Python作为一种广泛使用的高级编程语言&#xff0c;以其简洁的语法和强大的功能在文本处理领域占有一席之地。无论是数据清洗、自动化脚本编写&#xff0c;还是复杂的文本分析&#xff0c;Py…

不可编辑的加密word文件破解

文章目录 1 将word文件另存为xml格式2 使用记事本打开xml格式的word文件3 ctrlF查找w:enforcement4 将w:enforcement"1"改成w:enforcement"0"并保存5 用word打开xml格式的文件并另存为docx格式6 成功可以编辑 1 将word文件另存为xml格式 2 使用记事本打开x…

汽车电子行业知识:什么是电子后视镜

文章目录 1.什么是电子后视镜2.有哪些汽车用到了电子后视镜3.电子后视镜的原理及算法4.电子后视镜的优点5.电子后视镜的未来市场将继续增长 1.什么是电子后视镜 电子后视镜是一种集成了电子元件和显示屏的汽车后视镜&#xff0c;用于替代传统的机械后视镜。它通过内置的摄像头捕…

Open vSwitch 行为匹配的实现

一、Datapath 模块的行为匹配 在 Open vSwitch 的数据包转发流程中&#xff0c;存在快速路径和慢速路径两种模式&#xff0c;如下图所示&#xff1a; 其中&#xff0c;快速路径直接在 Datapath 模块完成行为匹配&#xff0c;将数据包转发出去。而慢速路径的数据包无法在 Datapa…

AI大模型到底有没有智能?一篇文章给你讲明明白白

生成式人工智能 (GenAI[1] ) 和大语言模型 (LLM [2] )&#xff0c;这两个词汇想必已在大家的耳边萦绕多时。它们如惊涛骇浪般席卷了整个科技界&#xff0c;登上了各大新闻头条。ChatGPT&#xff0c;这个神奇的对话助手&#xff0c;也许已成为你形影不离的良师益友。 然而&…

一次可输入多张图像,还能多轮对话!最新开源数据集,让AI聊天更接近现实

大模型对话能更接近现实了&#xff01; 不仅可以最多输入20张图像&#xff0c;还能支持多达27轮对话。可处理文本图像tokens最多18k。 这就是最新开源的超长多图多轮对话理解数据集MMDU&#xff08;Multi-Turn Multi-Image Dialog Understanding&#xff09;。 大型视觉语言模…

技术周总结2024.06.17~06.23(Doris数据库)

文章目录 一、06.18 周二1.1&#xff09; 问题01&#xff1a; doris数据表写入使用 stream load好还是 inser into好 一、06.18 周二 1.1&#xff09; 问题01&#xff1a; doris数据表写入使用 stream load好还是 inser into好 对于Doris数据表的写入操作&#xff0c;通常推荐…