位运算:如果一个问题能用到位运算,那么它的的运算性能可大大提升。
场景一:在一个无序数组里有99个不重复的正整数,范围是1~100,唯独缺少1个1~100中的整数。如何找出这个缺失的整数?
问题分析:方法一,可双重遍历得到缺失值,时间复杂度为O(n^2)。方法二,可预先将全部的正整数放入到创建的map中,遍历数组时再删除map中对应的值,最后留下来的值即为确实的值。方法三,先将100内的正整数累加,再减去数组中的全部正整数。
public class FindLostNum {
public static void main(String[] args) {
FindLostNum findLostNum = new FindLostNum();
int[] num = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(findLostNum.findLostNumV1(num));
num = new int[]{1, 2, 1, 2, 5, 6, 5, 5, 5};
System.out.println(findLostNum.findLostNumV2(num));
num = new int[]{1, 2, 1, 2, 5, 6, 5, 5, 5, 7};
Arrays.stream(findLostNum.findLostNumV3(num)).forEach(System.out::print);
}
private int findLostNumV1(int[] num) {
int sum = (1 + 10) * 10 / 2;
for (int i = 0; i < num.length; i++) {
sum -= num[i];
}
return sum;
}
}
场景二:一个无序数组里有若干个正整数,范围是1~100,其中99个整数都出现了偶数次,只有1个整数出现了奇数次,如何找到这个出现奇数次的整数?
问题分析:利用异或的性质,相同为0,相异为1。那么数组中相同的数异或则必为0,最后得到的结果即为出现奇数次的整数。
private int findLostNumV2(int[] num) {
int lost = 0;
for (int i = 0; i < num.length; i++) {
lost = lost ^ num[i];
}
return lost;
}
场景三:假设一个无序数组里有若干个正整数,范围是1~100,其中有98个整数出现了偶数次,只有2个整数出现了奇数次,如何找到这2个出现奇数次的整数?
问题分析:此问题难点在于有两个整数出现奇数次,如果我们能够找出这两个整数的区别,再套用场景二便可解决。这里用到了分治算法,设这两个整数为A,B。1.先将数组内元素异或得到A,B的异或值。2.将该值对应的二进制位从右至左找到第一个为1的值sep,表示A,B对应的二进制表示在此处的位置相异,设A为1,B为0。3.利用此区别,将数组中的其他元素和sep相与,为1和A划为一组,为0和B划为一组。4.利用场景二求解。
private int[] findLostNumV3(int[] num) {
int[] result = new int[2];
//查找两奇数次整数的异或值
int lost = 0;
for (int i = 0; i < num.length; i++) {
lost = lost ^ num[i];
}
//从右到左确定,确定分割点
int separator = 1;
while ((separator & lost) == 0) {
//separator左移一个单位
separator >>= 1;
}
//分治算法
for (int i = 0; i < num.length; i++) {
if ((separator & num[i]) == 0) {
result[0] ^= num[i];
} else {
result[1] ^= num[i];
}
}
return result;
}