• 计算机底层— — 异或运算的用处【2】


    异或运算

    1 概念及特性

    所谓异或运算就是:相同为0,不同为1【可以理解为无进位相加】

    异或的常用公式:

    • 0^N = N
    • N^N = 0

    异或是满足交换律和结合律的

    2 异或运算的基础用法

    在实际编程过程中,我们如果可以很好的使用异或操作,将大大提升我们算法的执行速度,大大降低时间复杂度与空间复杂度

    2.1 不用额外变量交换两个数

    public class Test01 {
        public static void main(String[] args) {
            int a = 10;
            int b = 1;
            a = a ^ b;
            b = a ^ b; // (a ^ b) ^ b = a
            a = a ^ b; // (a ^ b) ^ a = b
            System.out.println("a = " + a);
            System.out.println("b = " + b);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.2 找出现奇数次的数

    题目:一个数出现了奇数次,其他都出现了偶数次,找到出现奇数次的数

    根据异或的特性,N^N=0;并且0异或N=N,因此,只要一直异或下去就能找到最后的奇数

    public class Test01 {
        public static void main(String[] args) {
            int[] arr = {1,2,5,2,1,6,6,1,2,2,1};
            int num = findOddNum(arr);
            System.out.println(num);
        }
    
        public static int findOddNum(int[] arr){
            int eor = 0;
            for(int num : arr){
                eor ^= num;
            }
            return eor;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3 异或的高阶用法

    首先,我们需要先学会如何将一个int数的最右侧的1提取出来
    方法:取反加1【取相反数;相当于把右侧的1全部打散再加1来聚合】

    // eor最右侧的1,提取出来
    // eor :     00110010110111000
    // rightOne :00000000000001000
    
    • 1
    • 2
    • 3

    3.1 两个奇数次的数

    题目: 一个数中有两个数字出现了奇数次,其他为偶数次,找到这两个数

    假设这两个出现奇数次的数为a、b

    首先,遍历数组,进行异或,最终得到结果为:a^b = eor
    然后,取出a^b最右侧的1,可知在该位置a与b一个为1一个为0
    遍历数组,找到最右侧也为1的数字,然后与eor(a^b)异或
    最终可以得到a【两两异或为0,如果还剩下一个,那么一定为a】
    最后a与eor异或(a ^ (a^b)),得到b
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码实现:

    public class Test01 {
        public static void main(String[] args) {
            int[] arr = {2,3,2,1,5,5,7,6,6,7};
            findOddNum2(arr);//odd1:3 odd2:1
        }
    
        public static void findOddNum2(int[] arr){
            int eor = 0;
            for (int num : arr) {
                eor ^= num;
            }
            int rightOne = eor & (-eor);//获取到eor最右侧的1【可a与b在该位置上不相同】
            int odd1 = 0;//eor'
            for (int i = 0; i < arr.length; i++) {
                //  arr[1] =  111100011110000
                // rightOne=  000000000010000
                if((arr[i] & rightOne) != 0){
                    odd1 ^= arr[i];
                }
            }
            System.out.println("odd1:" + odd1 + " " + "odd2:" + (eor ^ odd1));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3.2 一个数K次、多个数M次问题

    一个数组中有一种数出现K次,其他数出现了M次数;M > 1, K < M
    找到:出现了K次的数
    要求:额外空间复杂度为O(1),时间复杂度为O(N)

    思路:

    • 因为是int类型,所以我们可以构建一个长度为32的int类型数组,用来记录int类型每位上1的信息(1出现的次数)
    • 假设每位上1的次数是count, count % M 取余,得到的就是目标数在该位置上出现的K次数

    3.2.1 构建数组int并求解

    /**
     * 出现k次的数
     * @param arr
     * @param k
     * @param m
     * @return
     */
    public static int onlyKTimes(int[] arr, int k, int m){
        int[] t = new int[32];
        //t[0] 0位置上的1出现了几个
        //t[i] i位置上的1出现了几个
        for (int num : arr) {
            for (int i = 0; i <= 31; i++) {
                t[i] += (num >> i) & 1;
            }
        }
        int ans = 0;
        //如果这个出现了K次的数就是0
        //那么下面代码中的ans |= (1 << i)就不会发生
        //那么ans就会一直维持0,最后返回0,也是正确的
        for (int i = 0; i < 32; i++) {
            if(t[i] % m == 0){
                continue;
            }
            if(t[i] % m == k){
                ans |= (1 << i);
            } else {
                return -1;
            }
        }
        return ans;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    3.2.2 构建另一个方法(通过map返回正确值)

    用来构建对数器【使用结果正确,但是时间、空间复杂度不那么好的算法来当样板】

    public static int test(int[] arr, int k, int m){
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int num : arr) {
            if (map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            }else{
                map.put(num, 1);
            }
            }
        int ans = 0;
        for(int num : map.keySet()){
            if(map.get(num) == k){
                ans = num;
                break;
            }
        }
        return ans;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.2.3 构建对数器

    //构建对数器
    
    /**
     * 随机生成数组
     * @param maxKinds 最多有几种数
     * @param range 数的范围
     * @param k k次
     * @param m m次
     * @return
     */
    public static int[] randomArray(int maxKinds, int range, int k, int m){
        //真命天子[出现k次的数]
        int kTimeNum = randomNumber(range);
        //真命天子出现的次数
        int times = k;
        int numKinds = (int)(Math.random() * maxKinds) + 2;//保证至少有两种数
        int[] arr = new int[times + (numKinds - 1) * m];//数组总长度:1个数k次 + (numKinds - 1)个数m次
        int index = 0;
        //依次填充出现k次的数
        for(; index < times; index++){
            arr[index] = kTimeNum;
        }
        numKinds--;
        HashSet<Integer> set = new HashSet<>();
        set.add(kTimeNum);
        while(numKinds != 0){
            int curNum = 0;
            //随机生成数,保证不重复
            do{
                curNum = randomNumber(range);
            } while(set.contains(curNum));
            set.add(curNum);
            numKinds--;
            for (int i = 0; i < m; i++) {
                arr[index++] = curNum;
            }
        }
    
        //arr填充完成
        //打乱数组中数字顺序
        for (int i = 0; i < arr.length; i++) {
            //i位置的数,随机与j位置上的数交换
            int j = (int)(Math.random() * arr.length);// 0 ~ N-1
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        return arr;
    }
    
    //随机生成[-range, range]的数
    public static int randomNumber(int range){
        return (int)(Math.random() * (range+1)) - (int)(Math.random() * (range+1));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    3.2.4 全部代码

    package com.ali.math;
    
    import java.util.HashMap;
    import java.util.HashSet;
    
    public class Test01 {
        public static HashMap<Integer, Integer> map = new HashMap<>();
        // 为了测试
        public static void main(String[] args) {
            int kinds = 5;
            int range = 30;
            int testTime = 100000;
            int max = 9;
            System.out.println("测试开始");
            for (int i = 0; i < testTime; i++) {
                int a = (int) (Math.random() * max) + 1; // a 1 ~ 9
                int b = (int) (Math.random() * max) + 1; // b 1 ~ 9
                int k = Math.min(a, b);
                int m = Math.max(a, b);
                // k < m
                if (k == m) {
                    m++;
                }
                int[] arr = randomArray(kinds, range, k, m);
                int ans1 = test(arr, k, m);
                int ans2 = onlyKTimes(arr, k, m);
                if (ans1 != ans2) {
                    System.out.println(ans1);
                    System.out.println("出错了!");
                }
            }
            System.out.println("测试结束");
        }
        public static int test(int[] arr, int k, int m){
            HashMap<Integer, Integer> map = new HashMap<>();
            for (int num : arr) {
                if (map.containsKey(num)) {
                    map.put(num, map.get(num) + 1);
                }else{
                    map.put(num, 1);
                }
                }
            int ans = 0;
            for(int num : map.keySet()){
                if(map.get(num) == k){
                    ans = num;
                    break;
                }
            }
            return ans;
        }
    
        /**
         * 出现k次的数
         * @param arr
         * @param k
         * @param m
         * @return
         */
        public static int onlyKTimes(int[] arr, int k, int m){
            int[] t = new int[32];
            //t[0] 0位置上的1出现了几个
            //t[i] i位置上的1出现了几个
            for (int num : arr) {
                for (int i = 0; i <= 31; i++) {
                    t[i] += (num >> i) & 1;
                }
            }
            int ans = 0;
            //如果这个出现了K次的数就是0
            //那么下面代码中的ans |= (1 << i)就不会发生
            //那么ans就会一直维持0,最后返回0,也是正确的
            for (int i = 0; i < 32; i++) {
                if(t[i] % m == 0){
                    continue;
                }
                if(t[i] % m == k){
                    ans |= (1 << i);
                } else {
                    return -1;
                }
            }
            return ans;
        }
    
    
    
        //构建对数器
    
        /**
         * 随机生成数组
         * @param maxKinds 最多有几种数
         * @param range 数的范围
         * @param k k次
         * @param m m次
         * @return
         */
        public static int[] randomArray(int maxKinds, int range, int k, int m){
            //真命天子[出现k次的数]
            int kTimeNum = randomNumber(range);
            //真命天子出现的次数
            int times = k;
            int numKinds = (int)(Math.random() * maxKinds) + 2;//保证至少有两种数
            int[] arr = new int[times + (numKinds - 1) * m];//数组总长度:1个数k次 + (numKinds - 1)个数m次
            int index = 0;
            //依次填充出现k次的数
            for(; index < times; index++){
                arr[index] = kTimeNum;
            }
            numKinds--;
            HashSet<Integer> set = new HashSet<>();
            set.add(kTimeNum);
            while(numKinds != 0){
                int curNum = 0;
                //随机生成数,保证不重复
                do{
                    curNum = randomNumber(range);
                } while(set.contains(curNum));
                set.add(curNum);
                numKinds--;
                for (int i = 0; i < m; i++) {
                    arr[index++] = curNum;
                }
            }
    
            //arr填充完成
            //打乱数组中数字顺序
            for (int i = 0; i < arr.length; i++) {
                //i位置的数,随机与j位置上的数交换
                int j = (int)(Math.random() * arr.length);// 0 ~ N-1
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
            return arr;
        }
    
        //随机生成[-range, range]的数
        public static int randomNumber(int range){
            return (int)(Math.random() * (range+1)) - (int)(Math.random() * (range+1));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
  • 相关阅读:
    golang中map赋值
    百看不如一练系列 32个python实战项目列表,得不到就毁掉
    es安装中文分词器
    在阿里云 linux 服务器上查看当前服务器的Nginx配置信息
    vue使用elementUI的upload上传文件封装
    PMP考试提分必刷题
    APS: frepple 开源解读
    离子液体[C7MIm]TfS 1-庚基-3-甲基咪唑三氟甲磺酸盐 齐岳bio
    数据结构—图论的习题
    java 的抽象(abstract)和接口(interface)的区别
  • 原文地址:https://blog.csdn.net/weixin_45565886/article/details/126189292