数组中只出现一次的数字 涉及到异或 与 操作

题目描述

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

方法1、 考虑hashmap

HashMap储存元素,遇到重复的就消除,遇到不一样的就存储,一次遍历即可

时间复杂度为O(n),估计用排序后二分/递归查找更快

import java.util.Iterator;
import java.util.LinkedHashMap;
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        LinkedHashMap<Integer,Integer> map=new LinkedHashMap<>();
        for(int i=0;i<array.length;i++){
            if(!map.containsKey(array[i])){
                map.put(array[i],1);
                 
            }else{
                map.remove(array[i]);
            }
        }
               Iterator iter = map.keySet().iterator();//迭代器
               num1[0]= (Integer)iter.next();//第一个key给 num1[0]
               num2[0]=(Integer)iter.next();//第二个key给 num2[0]
         
    }
}

方法二、 异或         符号为:   

/**
 * 1)两个相同数字异或=0,一个数和0异或还是它本身。
 * 2)当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,
 * 最后剩下的就是落单的数,因为成对儿出现的都抵消了。
 * 3)假设数组中有两个数字(A 和B)不一样,其他数字都成对出现,对这个数组的元素从一个逐个与后面的异或,最后的结果为A^B
    (A与B异或)
 */

 例如,

1)对于数组int[]a={9,3,3,4,0,6,4,6};   执行下面代码,最终bitResult=9; 

从结果看,就是数组9与0异或的结果

 for(int i = 0; i < length; ++i){//code 1
            bitResult ^= array[i]; 
        }

 2)如果能够对数组进行分类,把这两个只出现一次的数字假设为A和B)分到两个数组中,其他相同的元素也是成对的出现在两个数组中,那就好办了。分别对两个数组中的元素按照code1进行异或,最终在两个数组中各得到一个原数组中只出现一次的数字;

问题是如何分组?

我们考虑到一个数字的某一位二进制只能是1或者0,可以按照这个特性,把数字进行分组。且这个分组能够区分A和B,考虑到A和B不一样。

我们在结果数字bitResult中找到第一个为1 的位的位置,记为第N 位。可以把数组按照第N位是否为1,来进行区分。

例如:int[]a={9,3,3,4,0,6,4,6}  , bitResult =9 二进制表示为10001,说明第0位为1,可以按此区分数组

{9,3,3}  {4,0,6, 4, 6}  然后分别对两个数组取异或,得到 9 和0  即原数组中只出现一次的数字。

(从二进制的第0位开始往左边找,第一个1,两个数在此位一个有1,另一个一个没有)

3)我们知道根据结果数字bitResult  二进制第一个为1 的位置进行分组,首先要计算这个1是在bitResult  二进制中的第几位

考察的知识点:

1、布尔值“(number &1)==0”是什么意思? 

& 是一个位操作符号,可以确定数字是偶数还是奇数,或者说确定num最后一位是否位1

可参考:https://cloud.tencent.com/developer/ask/108296

代码如下

 /**
     *从左向右移动,获取第一个为1的位数   位数从右往左 ,第一个位标号是0位
     * (bitResult & 1) == 0)  判断true or false
     * @param bitResult  例如bitResult =9 二进制表示为10001 ,9&1==1.return index =0,说明第0位为1
     *                   再例如bitResult=8,二进制表示为1000,8&1==0,为true ,执行bitResult>>=1,
     *                   往右移动一位,即bitResult=100,或者说bitResult= 8 /2   ,index=1;
     *                   继续 true, 即bitResult=10,或者说bitResult= 4/2   ,index=2;
     *                   继续 true   即bitResult=1,或者说bitResult= 42/2   ,index=3;
     *                   继续  为false 跳出循环,index =3,说明第一个为1的位数为从右往左的标号为3
     * @return   index  第一个为1的位数
     */
    private int findFirst1(int bitResult){//获取第一个为1的位数
        int index = 0;
        while(((bitResult & 1) == 0) && index < 32){
            bitResult >>= 1;
            index++;
        }
        return index;//第一次 return  index =0
    }

4)计算   这个1是在bitResult  二进制中的第N位 后,就可以对原数组进行分组   

若target第N为有1,则往右移N位后,(target >> index) & 1==1 ,返回true ,表明arr[i] 应该划分到第N为有1的那一组,

返回true  划分到另一组                   0B开头表示二进制

关于target >> index的计算      例如  targe=4= 0B100  index=2,  0B100>>2==0B1  ==1    

private boolean isBit1(int target, int index){// isBit1(array[i], index)) //  code2
    return ((target >> index) & 1) == 1;
}

 code  2     isBit1(array[i], index))提供分组依据,code3 对组内进行异或处理,得到的结果分别数两个只出现一次的数字

/** code 3
 * 把原数组分成了两个子数组num1 和num2,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次
 *再对数组num1 和num2分别异或,得到的结果分别数两个只出现一次的数字
 */
for(int i = 0; i < length; ++i){
    if(isBit1(array[i], index)){
        num1[0] ^= array[i];//如果该位1,则划分到那一位都为1的一个数组
    }else{
        num2[0] ^= array[i];//否则,移到另一组
    }
}

    5) 完整代码含测例:

package pag1;
import java.util.Arrays;
public class SolutionListNode {
    public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2)    {
        int length = array.length;
        if(length == 2){
            num1[0] = array[0];
            num2[0] = array[1];
            return;
        }
        int bitResult = 0;
        /**
         * 1)两个相同数字异或=0,一个数和0异或还是它本身。
         * 2)当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,
         * 最后剩下的就是落单的数,因为成对儿出现的都抵消了。
         * 3)假设有两个数字(A 和B)不一样,其他数字都成对出现,则 code 1循环结束后,bieResult 的结果为A^B的结果,
         * 这个结果的二进制中的1,表现的是A和B的不同的位。
         */
        for(int i = 0; i < length; ++i){//code 1
            bitResult ^= array[i];//那举例的数据来说,最终bitResult=9;
        }
        int index = findFirst1(bitResult);//得到index的值,可以根据1的位进行分组,且A和B必定不在同一组
        /**
         * 把原数组分成了两个子数组num1 和num2,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次
         *再对数组num1 和num2分别异或,得到的结果分别数两个只出现一次的数字
         */
        for(int i = 0; i < length; ++i){
            if(isBit1(array[i], index)){
                num1[0] ^= array[i];//如果该位1,则划分到那一位都为1的一个数组
            }else{
                num2[0] ^= array[i];//否则,移到另一组
            }
        }
        System.out.println(Arrays.toString(num1));
        System.out.println(Arrays.toString(num2));
    }

    /**
     *从左向右移动,获取第一个为1的位数   位数从右往左 ,第一个位标号是0位
     * (bitResult & 1) == 0)  判断true or false
     * @param bitResult  例如bitResult =9 二进制表示为10001 ,9&1==1.return index =0,说明第0位为1
     *                   再例如bitResult=8,二进制表示为1000,8&1==0,为true ,执行bitResult>>=1,
     *                   往右移动一位,即bitResult=100,或者说bitResult= 8 /2   ,index=1;
     *                   继续 true, 即bitResult=10,或者说bitResult= 4/2   ,index=2;
     *                   继续 true   即bitResult=1,或者说bitResult= 42/2   ,index=3;
     *                   继续  为false 跳出循环,index =3,说明第一个为1的位数为从右往左的标号为3
     * @return   index  第一个为1的位数
     */
    private int findFirst1(int bitResult){//获取第一个为1的位数
        int index = 0;
        while(((bitResult & 1) == 0) && index < 32){
            bitResult >>= 1;
            index++;
        }
        return index;//第一次 return  index =0
    }

    /**
     * @param target 数组中的某个元素arr[i]
     * @param index  第一个位数位1的位置
     * @return    为了检验该数字在某一位是否位1  ,从而进行区分
     *             如果该位1,则划分到那一位都为1的一个数组
     */
    private boolean isBit1(int target, int index){
        int nt=target >> index;
        return (nt & 1) == 1;
    }

    public static void main(String[] args) {
        SolutionListNode test=new SolutionListNode();
        int[]a={9,3,3,4,0,6,4,6};
        int []num1=new int[1];
        int [] num2=new int[1];
        test.FindNumsAppearOnce(a,num1,num2);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值