美文网首页
数组中只出现一次的数字

数组中只出现一次的数字

作者: 囧略囧 | 来源:发表于2020-02-17 10:47 被阅读0次

    题目描述

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

    解法一:

    由于出现了数字与次数这样的对应关系,很自然想到使用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<Integer, Integer> map = new HashMap<Integer, Integer>();
            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

    相关文章

      网友评论

          本文标题:数组中只出现一次的数字

          本文链接:https://www.haomeiwen.com/subject/qphvqctx.html