那些异或相关常见的算法题

289 阅读3分钟

先抛出两个问题:

  1. 一个数组arr中,只有一个数的个数是奇数个,剩下的都是偶数个,问这个数是多少?
  2. 一个数组arr中,有两个数的个数是奇数个,剩下的都是偶数个,问这俩数是多少?

以上均要求空间复杂度为O(1)

先暂停想一分钟……

异或性质

  • 异或就是相同为0,不同为1,比如二进制 1001 和 1101 异或运算后,得到0100。
  • 异或运算可以理解为无进位的二进制加法。
  • 0和任意数异或的结果均为0,比如0^4=4。
  • 相同的两个数异或结果为0,比如 2^2= 0。
  • 异或运算满足交换律和结合率。

问题一:

若arr为 [1,1,1,2,2,3,3,4,4],那么很明显1就是我们要找的那个数,怎么不用额外的空间知道呢?

利用相同的两个数异或运算结果为0的性质即可,比如2^2=0,3^3=0。

那么偶数个的数异或结果为0,奇数个的数异或的结果为它自己,比如3^3^3 = 0^3 = 3。

那么我们令一个变量为eor,挨个和数组中的元素遍历,那最后的结果就是我们要找的只有一个数的个数是奇数个的数。

public class XOR {
    public static void main(String[] args) {
        int[] arr = new int[] {1, 1, 1, 2, 2, 3, 3, 4, 4};
        getOnlyOddNumber(arr);
    }

    public static void getOnlyOddNumber(int[] arr) {
        int eor = 0;
        for (int item : arr) {
            eor ^= item;
        }
        System.out.println(eor);
    }
}

问题二:

若arr为 [1,1,1,2,2,2,3,3,4,4,5,5],那么观察可得1和2是我们要找的答案,怎么不用额外的空间得到呢?

如果继续沿用问题一的思想,定义一个变量eor去遍历数组元素进行异或运算,结果得到的应该是1^2,但我们需要的是两个数,怎么拆开呢?

将1和2泛化成a和b,首先a^b != 0,那一定说明a和b两个数在二进制的某位上一定不相等,也就是a^b的结果二进制的某位上必然有个1,这样结果才 !=0

那假设a和b的第8位不相等,a第8位是1,b第8位是0,那arr可以被分为两类:

  • 第8位为1:a,其他
  • 第8位不为1:b,其他

现在用一个新的变量probe,去遍历数组中第8位为1的数进行异或运算,结果就是a。

而eor遍历运算后的结果为a^b,probe结果为a,那eor^probe = a^b^a = b,b成功得到。

public class XOR {
    public static void main(String[] args) {
        int[] arr2 = new int[] {1, 1, 1, 2, 2, 2, 3, 3, 4, 4};
        getTwoOddNumber(arr2);
    }

    public static void getTwoOddNumber(int[] arr) {
        int eor = 0;
        for (int item : arr) {
            eor ^= item;
        }
        // 提取最右的1
        int rightOne = eor & (~eor + 1);
        int probe = 0;
        for (int item : arr) {
            if ((item & rightOne) == 0) {
                probe ^= item;
            }
        }
        System.out.println(probe + " " + (eor ^ probe));
    }
}

使用异或实现swap函数

private static void swap(int[] arr, int x, int y) {
    // 使用异或性质交换
    arr[x] = arr[x] ^ arr[y];
    arr[y] = arr[x] ^ arr[y];
    arr[x] = arr[x] ^ arr[y];
}