异或运算和几个经典题目解析

认识异或运算

异或运算就记成无进位相加

0 1 1

1 0 1

1 1 0

异或运算满足交换律和结合律

同样一批数,不管选择什么样的顺序做异或运算,最后结果一定是一个

如何不使用额外的变量交换两个数

1)a = a ^ b

2) b = a ^ b

3) a = a ^ b

比如说a = 甲,b = 乙

1)a = 甲 ^ 乙,b = 乙

2)b = 甲^乙^乙,a = 甲 ^ 乙

3)a = 甲^乙^甲,b = 甲

public static void swap(int[] arr,int i,int j){
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

能够使用异或运算的前提是,两个数的交换,不能是同一个位置

只有两个内存区域是分开的,你才能这么玩

如果要在arr 里交换 i位置跟j位置的数, 如果i位置和j位置是同一个位置, 则用异或交换是错误的你是一个内存区域,你跑完这三行代码之后,就把自己刷成0

一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这个数

用eor异或所有人, 把最后结果留在自己身上, 这么干完一遍之后,最后你返回eor的值就是那个出现了奇数次的数
public static void printOddTimesNum1(int[] arr){
    int eor = 0;
    for(int i = 0;i < arr.length;i++){
        eor ^= arr[i];    
    }
}

把一个int类型的数,提取出最右侧的1出来

int rightOne = eor & (~eor + 1);

 

例如:

eor = 0110 1110 0100 0000

~eor = 1001 0001 1011 1111

~eor+1 = 1001 0001 1100 0000

& = 0000 0000 0100 0000

此时的结果就是最右侧的1

一个数组中有两种数出现奇数次,其他数出现偶数次,怎么找到并打印这个数

public static void printOddTimesNum1(int[] arr){
    int eor = 0;
    for(int i = 0;i < arr.length;i++){
        eor ^= arr[i];//此时得到的eor = a^b,偶数次都为0了,所以最后就剩a^b    
    }
    //又因为a != b,a ^ b != 0,所以a,b两个数中肯定有一位不相同
    int rightOne = eor & (~eor + 1);//提取出最右侧数
    int onlyOne = 0;
    //循环得到的结果是a or b
    for(int i = 0;i < arr.length;i++){
        if((arr[i] & rightOne) != 0){
            onlyOne ^= arr[i];        
        }    
    }
    sout(onluOne + (eor ^ onlyOne));
    
}

 

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

题意

异或运算和几个经典题目解析_第1张图片

异或运算和几个经典题目解析_第2张图片

题解

准备一个长度为 32 的数组, 固定长度的数组, 依然额外空间复杂度O(1)第一步: 把所有数字二进制状态每个位置的1都累加到t里

异或运算和几个经典题目解析_第3张图片

任何一个整数都可以转成数组形式的二进制状态

如果 a, b, c, 出现7次, d出现3次, 如果第0位不是7的整数倍, 意味着d 在 0位一定是1, 如果是7的整数倍, 意味着d 在 0位一定不是1, 因为K>M

异或运算和几个经典题目解析_第4张图片

1出现7次, 3出现7次, 5出现3次, 二进制表达在第零位相加为17次, 不是7的整数倍,说明你要求的这个出现了3次的数在第零位肯定是1通过每一位上是否是7的整数倍,来把出现较小次数的数在这位是不是1确定下来

异或运算和几个经典题目解析_第5张图片

- 可以最后通过每一位上是否是 M 的整数倍这件事就把那个出现了较小次数的数给他依次给弄出来- 如果我们在某一个变量发现 1 的数量不能够被 M 整除,说明我要找的那个数,在这一位它一定是 1 状态。- 如果我们在某一个变量发现 1 的数量能够被 M 整除,说明我要找的那个数,在这一位它一定是0 状态。

如果零这个数出现K次, 则需要特殊处理

public class Code03_KM {

    public static int test(int[] arr, int k, int m) {
        HashMap map = new HashMap<>();
        for (int num : arr) {
            if (map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            } else {
                map.put(num, 1);
            }
        }
        for (int num : map.keySet()) {
            if (map.get(num) == k) {
                return num;
            }
        }
        return -1;
    }

    public static HashMap map = new HashMap<>();

    // 请保证arr中,只有一种数出现了K次,其他数都出现了M次
    public static int onlyKTimes(int[] arr, int k, int m) {
        if (map.size() == 0) {
            mapCreater(map);
        }
        int[] t = new int[32];
        // t[0] 0位置的1出现了几个
        // t[i] i位置的1出现了几个
        for (int num : arr) {
            while (num != 0) {
                int rightOne = num & (-num);
                t[map.get(rightOne)]++;
                num ^= rightOne;
            }
        }
        int ans = 0;
        for (int i = 0; i < 32; i++) {
            if (t[i] % m != 0) {
                if (t[i] % m == k) {
                    ans |= (1 << i);
                } else {
                    return -1;
                }
            }
        }
        if (ans == 0) {
            int count = 0;
            for (int num : arr) {
                if (num == 0) {
                    count++;
                }
            }
            if (count != k) {
                return -1;
            }
        }
        return ans;
    }

    public static void mapCreater(HashMap map) {
        int value = 1;
        for (int i = 0; i < 32; i++) {
            map.put(value, i);
            value <<= 1;
        }
    }

    public static int[] randomArray(int maxKinds, int range, int k, int m) {
        int ktimeNum = randomNumber(range);
        // 真命天子出现的次数
        int times = Math.random() < 0.5 ? k : ((int) (Math.random() * (m - 1)) + 1);
        // 2
        int numKinds = (int) (Math.random() * maxKinds) + 2;
        // k * 1 + (numKinds - 1) * m
        int[] arr = new int[times + (numKinds - 1) * m];
        int index = 0;
        for (; index < times; index++) {
            arr[index] = ktimeNum;
        }
        numKinds--;
        HashSet 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 tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }
        return arr;
    }

    // [-range, +range]
    public static int randomNumber(int range) {
        return (int) (Math.random() * (range + 1)) - (int) (Math.random() * (range + 1));
    }

    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(ans2);
                System.out.println("出错了!");
            }
        }
        System.out.println("测试结束");

    }

}

 

你可能感兴趣的:(数据结构与算法,链表,散列表,fpga开发,异或)