先抛出两个问题:
- 一个数组arr中,只有一个数的个数是奇数个,剩下的都是偶数个,问这个数是多少?
- 一个数组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];
}