问题描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
解题思路
问题用HashMap解决是很容易想到的。使用异或运算的特点也可以解决该问题。
HashMap
- 将数组的值作为key存入HashMap:
——1)当该值存在于HashMap中,删除它
——2)当该值不存在加入它 - 最后整个HashMap中剩下的就是结果的两个值。遍历一次数组即可。由于只有两个重复的元素,所以HashMap的存储、查询效率都为O(1),这个比利用ArrayList下标来标记的效率高很多,因为当数组中某个数字很大时,ArrayList的空间就会极大浪费。Hash的方法是完全优于ArrayList下标标记的方法的。
位运算
- 这里有个很特殊的条件,所有数字都出现了两次,这意味着我们把数组中所有数组依次进行异或运算,结果值一定是两个出现一次的数字异或的结果,因为两个相同的数异或的结果为0。
- 但是我们并不知道两个数字中哪些位置上为1,哪些位置为0。
- res &= (-res) 计算这个可以将res的最低为1的数字保留,其他高位清零。这是利用补码的特点,读者可以自己取一个数试一下。
- 比如1010的补码是0110,二者与的结果是0010。拿到这个结果我们将数组中的数字分成两类,一类是和res与为1,一类是和res与为0。我们的结果一定分布在两个类中(一类一个)。
- 因为其他数重复两次, 所以我们继续对这两类数进行异或操作,那么一个类中的重复数字(重复的数字一定会分在一个类中)就会相互抵消,异或最后的结果就是我们要的值。
Code
// HashMap优化 时间复杂度为O(n) 空间复杂度为O(n)
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
HashMap<Integer,Integer> hm = new HashMap();
for (int i = 0; i < array.length; i++) {
if (!hm.containsKey(array[i])) hm.put(array[i],1);
else hm.remove(array[i]);
}
Set<Integer> st = hm.keySet();
Iterator<Integer> it = st.iterator();
num1[0] = it.next();
num2[0] = it.next();
}
//位运算解法 时间复杂度为O(2n) 空间复杂度为O(1)
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int res = 0;
for (int i = 0; i < array.length; i++) res ^= array[i];
res &= (-res);
for (int i = 0; i < array.length; i++) {
if ((array[i] & res) == res) num1[0] ^= array[i];
else num2[0] ^= array[i];
}
}