ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

算法之异或运算及其应用

2021-10-09 22:34:16  阅读:193  来源: 互联网

标签:arr 运算 int 算法 eor 异或 数字 位为


算法之异或运算及其应用

基本介绍

异或算法又可称为无进位加法

1 ^ 1 = 0 ( 1 + 1 = 10 ,如果不进位的话,那结果就是0 )

1 ^ 0 = 1 ( 1 + 0 = 1 )

0 ^ 1 = 1 ( 0 + 1 = 1 )

0 ^ 0 = 0 ( 0 + 0 = 0 )

特性

满足交换律和结合律,表明计算结果和异或顺序无关

N ^ 0 = N N ^ N = 0

应用 - 快速交换值

1. 代码实现

交换 a 与 b 的值

  private void swap(int a, int b) {
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
    }

2. 好处

按照上述方式进行值的交换,就无需开辟一个新的空间(不用创建一个变量来辅助进行值的交换)

3. 原理说明

需要使用到的知识为:

异或运算结果和顺序无关

N ^ 0 = N N ^ N = 0

说明如下:

譬如 a = 甲,b = 乙

① a = a ^ b => 此时 a = 甲 ^ 乙 , b = 乙

② b = a ^ b => 此时 a = 甲 ^ 乙 , b = 甲 ^ 乙 ^ 乙 = 甲 ^ 0 = 甲

③ a = a ^ b => 此时 a = 甲 ^ 乙 ^ 甲 = 甲 ^ 甲 ^ 乙 = 0 ^ 乙 = 乙 , b = 甲

通过分析,我们可以得到,经过这三步后, a 和 b 的值确实交换了

4. 使用前提

使用这个看似很高逼格的交换前提是 a 和 b 不能指向同一空间 ( 它们的值可以相等,但不能指向同一空间 ), 不然 a 和 b 就都会变成 0 ,因为此时 a ^ b = a ^ a = 0

你没办法确保传进来的两个值一定是不同的( 在排序算法及其应用2 - 冒泡排序中我们曾用过这个交换方法,是因为我们确定传进来的两个数肯定不是指向同一空间的 ),所以这种抖机灵的写法是不推荐的,日常最好还是按照我们一般的交换方式来写

算法题

1. 问题

有一个数组[]

① 数组中只有一种数字出现奇数次,其他数字都出现偶数次

② 数组中有两种数字出现奇数次,其他数字都出现偶数次

找到出现奇数次的数字

要求: 时间复杂度为 O(n), 空间复杂度为 O(1)

2. 思路

对于第 ① 种情况,所求的奇数次的数字即为数组中每个元素进行异或后的结果

证明如下:

由异或运算的特性:运算顺序不影响异或结果,所以出现偶数次的数字经过异或后就会变成0,所以结果自然只剩下出现奇数次的数字

举个栗子: 比如 arr = {1, 3, 9, 6, 6, 3, 3, 1, 3} [ 其中唯一出现奇数次的数字为 9 ]

将所有元素异或后得到 1 ^ 3 ^ 9 ^ 6 ^ 6 ^ 3 ^ 3 ^ 1 ^ 3 = 1 ^ 1 ^ 3 ^ 3 ^ 3 ^ 3 ^ 6 ^ 6 ^ 9 = 0 ^ 9 = 9

对于第 ② 种情况,会比第一种难一些,实现思路如下:

首先将数组中每个元素进行异或,得到的结果就是两个奇数次的数字的异或,将这个结果赋值给 eor

由于这两个数字不一样,所以 eor != 0,这两个数字的二进制肯定有一位是不同的

假设它们的第 8 位不同,即得到的异或结果的第 8 位为 1,我们让 eor' = “第 8 位为 1,其他都为 0”,而数组中所有元素可以分为两组, 一组为 ‘第 8 位为 0’,另一组为 ‘第 8 位为 1’,而我们所要求的这两个数分别在这两组中( 不可能为同一个组,因为上面已经假设他们第 8 位是不同的,所以必然一个为 1,一个为 0 ) ,并且每一组中除了要求的两个数,其他数都是偶数次的

我们将数组元素分别与 eor' 进行 & 运算,将第 8 位上等于 1 (或 0)的数字筛选出来,进行异或运算,这样就可以得到其中一个出现奇数次的数 a

再将 eor 与 a 进行异或,即可得到另外一个出现奇数次的数 b

听上去是不是有些抽象,那我们来举个栗子~

比如 arr = {1, 3, 9, 6, 6, 3, 3, 1, 3, 7} [ 其中唯一出现奇数次的数字为 9 和 7 ]

第一步,先将所有元素进行异或: eor = 1 ^ 3 ^ 9 ^ 6 ^ 6 ^ 3 ^ 3 ^ 1 ^ 3 ^ 7 = 1 ^ 1 ^ 3 ^ 3 ^ 3 ^ 3 ^ 6 ^ 6 ^ 9 ^ 7 = 0 ^ 9 ^ 7= 9 ^ 7 = 14

9 的二进制 1 0 0 1

7 的二进制 ^ 1 1 1

​ -----------------

​ 1 1 1 0 ( 化为十进制就是14 )

第二步,我们看到 eor 的二进制结果中至少有一位不为 0,我们选出不为 0 的一位,譬如第 2 位, 那么 eor' = "第 2 位为1,其他都为 0" = 2

第三步,将 eor' 分别与数组元素进行 & 运算,就可以将元素分为了两组

1, 1 | 3, 3, 3, 3

9 | 6, 6

​ | 7

第 2 位为 0 第 2 位为 1

我们取与 eor' 异或结果为 0 的那一组(你想取结果为 0 或结果为 1 的都可以),即 第 2 位为 0 的那一组,将这组的元素进行异或运算,得到结果 a = 1 ^ 1 ^ 9 = 0 ^ 9 = 9

第四步,再将 a 与 eor 进行异或 ,即可得到结果 b = 9 ^ 14 = 7

a 和 b 即是我们要求的值

3. 代码

问题 ① 的代码

  public int getOneNum(int[] arr) {
        int eor = 0;
        for (int i : arr) {
            eor ^= i;
        }
        return eor;
    }

问题 ② 的代码

    public void getTwoNum(int[] arr) {
        int eor = 0;
        for (int i : arr) {
            eor ^= i;
        }
        // eor = 所求两个数的异或结果
        // eor 一定不为0, eor 必然有一位上是 1
        int rightOne = eor & (~eor + 1);    // 提取出最右位的1
        int onlyOne = 0;    // eor'
        for (int cur: arr){
            if ((cur & rightOne) == rightOne) { // 这里目的是为了分组, == 0 也是可以的
                onlyOne ^= cur;
            }
        }
        System.out.println(onlyOne + " " + (eor ^ onlyOne));
    }

说明:其中 int rightOne = eor & (~eor + 1); // 提取出最右位的1 用于得到某个数最右边位置上的1,是一种常规操作,要学会使用

欢迎大家来我博客逛逛 mmimo技术小栈

标签:arr,运算,int,算法,eor,异或,数字,位为
来源: https://www.cnblogs.com/mmimo/p/15388019.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有