用异或^可解此题。
但是首先要知道一个知识点,a^b^a = a^a^b = b^a^a =b,这个知识点也就是本题的简单版本:如果数组中除了某一个数字,其他数字都出现了两次,找出该数字。思路就是遍历数组,对每一个数字都求异或,最后得到的值就是要找的数字。
有了该知识点的储备,再来看看本题。本题是要找两个数字a和b,那我们把该数组分成两个数组,其中a和一部分出现两次的数字在一块儿,b和另一部分出现两次的数字在一块儿,那这两个数组不是就变成了上面讲的那个简单版本中的数组吗?然后再分别对这两个数组求异或,就可以找到这两个数字了。
举例:[a,2,2,3,3,b]。把该数组分成[a,2,2]和[3,3,b],再对这两个数组求异或,便能得到a和b。
问题:怎么把a和b区分开来?
可以利用二进制来区分。先对整个数组求异或得到c,根据上面的知识,可以知道c其实就是a^b=c。那么对于c,假如c二进制的第一位是1,其实就代表a二进制的第一位是1(或0),b二进制的第一位是0(或1),总而言之如果第一位的c等于1,那么a和b在第一位肯定不相等。
所以我们就可以想到利用二进制的第一位(有可能是第二位,第三位 。。。因为上面是假设的c第一位是1)为1来区分两个数组,第一位为1的是数组一,第一位为0的是数组二。这样就相当于把a和b给区分开来了。
a和b区分开以后,剩下的就简单了,判断数组中其他数字的二进制的第一位是否为1,是的话就分到数组一,为0就分到数组二。最后对数组一和数组二分别进行异或,得到的就是a和b。
有个地方没有讲清楚,利用二进制的第一位为1来区分两个数组,如果第一位不是1,那就判断第二位,第三位,一直到找到为1的地方。假设一直找到第n位才为1,那就判断数组中的其他数字的二进制的第n位是否为1,做&运算即可判断。
位运算感觉还是挺抽象的,我也是用具体的例子来照着别人的代码推一遍才搞明白,为什么那个地方做&运算就可以得到它,为什么那个地方做^运算又可以得到它。要是还没看懂的小伙伴也可以自己写一个具体的数组,然后照着代码先在草稿纸上推一遍,多搞几次就明白了。
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param array int整型一维数组 * @return int整型一维数组 */ public int[] FindNumsAppearOnce (int[] array) { /** * 异或的运算方法是一个二进制运算: * 1^1=0 * 0^0=0 * 1^0=1 * 0^1=1 */ int res = 0; //对整个数组求异或的结果 for (int i = 0; i < array.length; i++) { res ^= array[i]; } int temp = 1; //判断异或结果的二进制第一位是否为1,为1则直接跳过该循环 while ((temp & res) == 0) { //为0则继续往前找,一直到找到为1的二进制位 temp <<= 1; } int lastNumIs1 = 0; int lastNumIs0 = 0; for (int j = 0; j < array.length; j++) { //如果该数字二进制的第某位为0,则分到数组一 if ((array[j] & temp) == 0) { lastNumIs1 ^= array[j]; } else { //如果该数字二进制的第某位为1,则分到数组二 lastNumIs0 ^= array[j]; } } return lastNumIs1 < lastNumIs0 ? new int[]{lastNumIs1,lastNumIs0} : new int[]{lastNumIs0,lastNumIs1}; } }