数组中只出现1次的两个数

这里写图片描述

思路

首先我们可以考虑下这个题目的简化版——数组中除一个数字只出现1次外,其它数字都成对出现,要求尽快找出这个数字。根据异或运算的特点,直接异或一次就可以找出这个数字。

现在数组中有两个数字只出现1次,直接异或一次只能得到这两个数字的异或结果,但光从这个结果肯定无法得到这个两个数字。因此我们来分析下简化版中“异或”解法的关键点,这个关键点也相当明显——数组只能有一个数字出现1次。

设题目中这两个只出现1次的数字分别为A和B,如果能将A,B分开到二个数组中,那显然符合“异或”解法的关键点了。因此这个题目的关键点就是将A,B分开到二个数组中。由于A,B肯定是不相等的,因此在二进制上必定有一位是不同的。根据这一位是0还是1可以将A,B分开到A组和B组。而这个数组中其它数字要么就属于A组,要么就属于B组。再对A组和B组分别执行“异或”解法就可以得到A,B了。而要判断A,B在哪一位上不相同,只要根据A异或B的结果就可以知道了,这个结果在二进制上为1的位都说明A,B在这一位上是不相同的。
这里写图片描述

所有元素异或后,在找出最右边为1的时,主要利用正数原码和其对应相反数补码的关系:

对于一个数字x,x&(-x)之后得到的数字,是把x中最右边的1保留下来,其他位全部为0。注意,这里的-x是x的相反数。

package com.zhumq.leetcode;

import java.util.Arrays;

import org.junit.Test;

public class FindTwoNumsAppearOnce {
    /*
     * 返回一个数最低位的1,其他的各位都为0,利用补码和原码的关系
     */
    public int FindFirstBit1(int num) {
        return num&(-num);
    }

    /**
     * 根据res判断n的特定位是否为1
     * @param n 
     * @param res res中只有一位为1,是前面FirstBit1的返回值
     * @return
     */
    public boolean IsBit1(int n,int res) {
        return ((n&res)==0) ? false:true;
    }


    public int[] FindNumsAppearOnce(int arr[])
    {
        //数组为空或者元素个数下于2时返回null
        if(arr==null || arr.length<2)
            return null;

        //全部异或之后的值,即A,B异或后的值
        int allXor = 0;
        //全部异或,allXor保存异或的结果
        for(int i=0;i<arr.length;i++)
            allXor ^= arr[i];
        //最低位的1
        int res = FindFirstBit1(allXor);

        int num1 =0, num2 = 0;
        for(int i=0;i<arr.length;i++)
        {   
            /*
             * 根据最低位是否为1来划分异或
             *      相同的数字相同的位上的值是相同的,要么都为1,要么都为0,
             *      因此同样可以通过判断该位是否为1来将这些出现两次的数字划分到同一个子数组中,
             *      该位如果为1,就分到一个子数组中,如果为0,就分到另一个子数组中。
             *      不管划分的是否均匀,只要保证来个相同的元素分到同一边即可,即使两两相同的元素全分到同一边异或后结果也还是一样。
             */
            if(IsBit1(arr[i],res))
                num1 ^= arr[i];
            else
                num2 ^= arr[i];
        }
        return new int[] {num1,num2};
    }
    @Test
    public void test1() {
        int arr[] = {1,2,3,3,1,4,5,4,5,6};
        System.out.println(Arrays.toString(FindNumsAppearOnce(arr)));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值