数组中只出现一次的数字

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

解法一:

由于出现了数字与次数这样的对应关系,很自然想到使用Map存储这个键值对,key存储数字,value存储次数。最后遍历Map找到只出现一次的数字即可。
时间复杂度为O(n),空间复杂度为O(n)。

public class Solution {
    public static void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        boolean one = false;
        Map map = new HashMap();
        for(int i = 0; i < array.length; i++) {
            if(!map.containsKey(array[i])) {
                map.put(array[i], 1);
            }
            else {
                map.put(array[i], 2);
            }
        }
        for(Entry m : map.entrySet()) {
            if((int)m.getValue() == 1) {
                if(one == false) {
                    num1[0] = (int) m.getKey();
                    one = true;
                }
                else {
                    num2[0] = (int) m.getKey();
                }
            }
        }
    }
}
解法二:

如果要求时间复杂度为O(n),空间复杂度为O(1)呢?很明显解法一便不符合要求。

这里涉及到位运算的知识。异或的性质是同一个数字与自己异或为0,与0异或为自己本身。

那么如果数组中只有一个只出现一次的数字的话,将数组从头开始异或,最后得到的便是只出现一次的数字,其他的数字在与自身异或的时候被消除掉了。

如果我们可以将题目提供的数组分成两部分,这两个子数组中均只含有一个只出现一次的数字,其他数字均出现两次。则问题便得到了解决。

设两个只出现一次的数字为a和b,将数组从头进行异或,最后得到的数字c一定不为0,因为该数字是a与b异或的结果,c中至少有一位1。取c中第一个为1的位的位置,根据这一位为1还是0将数组分为两部分,这样相同的数字一定在同一个子数组,a和b一定在两个子数组中。问题得到解决。

public class Solution {
    public static void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int product = 0;
        int index = 0;
        num1[0] = 0;
        num2[0] = 0;
        for(int i = 0; i < array.length; i++) {
            product ^= array[i];
        }
        //获得第一个为1的位置
        for(int i = 0; i < Integer.toBinaryString(product).length(); i++) {
            if(Integer.toBinaryString(product).charAt(Integer.toBinaryString(product).length() - 1 - i) == '1') {
                index = i;
                break;
            }
        }
        DecimalFormat df = new DecimalFormat("00000000000000000000000000000000");
        for(int i = 0; i < array.length; i++) {
            String binary = Integer.toBinaryString(array[i]);
            String format = df.format(Integer.valueOf(binary));       //出错点
            if(format.charAt(format.length() - 1 - index) == '1')
                num1[0] ^= array[i];
            else
                num2[0] ^= array[i];
        }
    }
}

寻找第一个为1的位置时必须从右往左找,因为通过Integer.toBinaryString()函数产生的二进制位数是不固定的,例如1的二进制为1,而2的二进制为10,这就导致从右开始数得到的位数各个数字是不对应的。

上述程序在数字较小的时候可以正常工作,但是若出现较大的数字比如1000000时,转变为二进制为11110100001001000000,在“出错点”步骤会出现溢出错误。

实际上出错的关键是String format = df.format(Integer.valueOf(binary));这行,format函数输入不支持String所以我们把String转为int。该函数的目的是统一二进制的位数,我们可以通过别的方式来统一位数。

public class Solution {
    public static void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int product = 0;
        int index = 0;
        num1[0] = 0;
        num2[0] = 0;
        for(int i = 0; i < array.length; i++) {
            product ^= array[i];
        }
        for(int i = 0; i < Integer.toBinaryString(product).length(); i++) {
            if(Integer.toBinaryString(product).charAt(Integer.toBinaryString(product).length() - 1 - i) == '1') {
                index = i;
                break;
            }
        }
        for(int i = 0; i < array.length; i++) {
            String binary = Integer.toBinaryString(array[i]);
            String tmp = "";
            //通过循环对位数进行补足
            if(binary.length() < 32) {
                tmp = "";
                for(int j = 0; j < 32 - binary.length(); j ++)
                    tmp += "0";
            }
            String format = tmp + binary;
            if(format.charAt(format.length() - 1 - index) == '1')
                num1[0] ^= array[i];
            else
                num2[0] ^= array[i];
        }
    }
}

上面的方法较为笨拙,位数补足的目的是为了便于判断相应位置是0还是1(若两个仅存在一次的数字a、b相异或的数字二进制为10,第一个为1的位置为2,判断数字4,二进制为100时不需要对100进行补足。但若a、b相异或的数字二进制为100,判断数字2,二进制为10时则需要对10进行补足。否则在从右往左判断的过程中10会溢出。)

我们可以通过位运算避免显式的位数补足。

public class Solution {
    public static void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int product = 0;
        int index = 0;
        num1[0] = 0;
        num2[0] = 0;
        for(int i = 0; i < array.length; i++) 
            product ^= array[i];
        //获得第一个为1的位置
        while((product & 1) == 0 && index < 8 * 4) { 
            product = product >> 1;
            index++;
        }
        for(int i = 0; i < array.length; i++) { 
             //通过移位判断相应位置是否为1 
            if(((array[i] >> index) & 1) == 0)
                num1[0] ^= array[i];
            else
                num2[0] ^= array[i];
        }
    }
}

1、通过依次右移1位和与1相与,可以很快得到第一个为1的位置,index < 8 * 4是因为JAVA中的int是32位的

2、通过直接右移index位并与1相与可以直接得到第index位是否为1

你可能感兴趣的:(数组中只出现一次的数字)