【剑指offer】40、数组中只出现一次的数字

本文介绍了一种算法挑战,即在一个整型数组中找到两个只出现一次的数字,其余数字均出现两次。提供了两种解决方案:使用HashMap和异或运算,强调了时间复杂度O(n)和空间复杂度O(1)的要求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

        一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

解题思路

方法一(hashmap法):

        遍历数组,将这个数组中的数字逐个存入到hash表中。

        如果hashmap中,没有这个数字,就将这个数字存入到hashmap中,是将这个数值作为键,然后将它的值置为1。

        如果hashmap中,已经有该数,就将这个数字移除。

        那么这样得到的结果就是:以那两个仅出现了一次的数字作为键,然后其对应的值是1。

        最后,申请数组空间,将这两个对应的键的数字(通过hashmap.keyset()取到),存入到数组的0和1位置上。

        便可以完成操作。

方法二(异或法):

        异或:两个相同的数字,进行异或,等到的结果是0。

        如果这个数组里面只有1个只出现一次的数,那把这个数组里面的数字全部异或一遍,就可以得到那个只出现了一次的数字。

        但是现在这个数组中有2个只出现一次的数,那怎么办呢?

        我们可以想到如果能把这数组分成两部分,每一部分中都只有一个仅出现一次的数字,然后这两部分分别取异或,是不是就可以得到那两个只出现了一次的数字呢?

         现在的问题是:怎么把这个数组进行分组呢?

         我们从头到尾对这个数字进行异或,最后可以得到的结果就是这两个数字取异或的结果。

         由于是两个不同的数字取异或,因此这些结果中一定至少有一位是1。我们把结果中是1的位置记为第n位,通过结果我们可以得知第n位上的数字一定是1和0。

          接下来,我们根据数组中每个数字的第n位上的数字是否为1来进行分组。

          若能将这个数组分为两个数组,其中,每一个数组中只包括一个只能出现一次的数字。然后将这两个数组分别从头到尾异或,那么就可以得到这两个数字了。          

测试用例

            功能测试(数组中有多对重复的数字;无重复的数字)

代码

package mySword;

import java.util.ArrayList;
import java.util.HashMap;

//一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。
// 请写程序找出这两个只出现一次的数字。
public class FindNumsAppearOnce {
    //法一:
    //    public static void FindNumsAppearOnce(int[] array, int[] num1, int[] num2){
//        ArrayList<Integer> list = new ArrayList<Integer>();
//        HashMap<Integer, Integer> temp = new HashMap<Integer, Integer>();
//        for(int i = 0; i < array.length; i++){
//            if(temp.containsKey(array[i])){
//                temp.remove(array[i]);
//            }else{
//                temp.put(array[i], 1);
//            }
//        }
//
//        int[] a = new int[array.length];
//        int i = 0;
//        for(Integer k : temp.keySet()){
//            a[i] = k;
//            i++;
//        }
//        num1[0] = a[0];
//        num2[0] = a[1];
//    }

    //法二:异或
//    public static void FindNumsAppearOnce(int[] array, int[] num1, int[] num2){
//        if(array == null || array.length < 2){
//            return;
//        }
//
//        //逐个异或,得到最终异或的结果。
//        //这个结果肯定不应该是1
//        int resultExclusiveOR = 0;
//        for(int i = 0; i < array.length; i++){
//            resultExclusiveOR ^= array[i];
//        }
//
//        //这块是为了找到1的下标
//        int indexOf1 = 0;
//        //如果最终的结果不是0(就是该结果会和1取与为0)而且这个数组的下标应该要小于32位
//        while(((resultExclusiveOR & 1) == 0) && (indexOf1 <= 4*8)){
//            resultExclusiveOR = resultExclusiveOR >> 1;//那么将这个数字右移
//            indexOf1 ++;//下标也跟着增加
//        }
//
//        //申请两个数组空间,并将它们初始化。
//        num1[0] = 0;
//        num2[0] = 0;
//
//        //0和任何数字异或都可以得到这个数字本身
//        for(int i = 0; i < array.length; i++){
//            if(((array[i] >> indexOf1)  & 1) == 1)
//                num1[0] ^= array[i];
//            else
//                num2[0] ^= array[i];
//        }
//    }


    //法二:异或
    public static int[] findNumsAppearOnce(int[] data){
        if(data == null || data.length < 2){
            return null;
        }

        int result = 0;
        for(int i = 0; i < data.length; i++){
            result ^= data[i];
        }

        int indexOf1 = findFirstOf1(result);
        int[] res = new int[]{0, 0};
        if(indexOf1 < 0){
            return res;
        }

        for(int i = 0; i < data.length; i++){
            if((data[i] & indexOf1) == 0){
                res[0] ^= data[i];
            }else{
                res[1] ^= data[i];
            }
        }
        return res;
    }

    //怎么找?如果数字是负的,直接就不用考虑了。
    //我们先令indexOf1的初值是1
    //当这个数字不为0的时候,
    //如果这个数与1取与结果为1的话,就可以返回,就证明它的第一个数字是1;
    //如果这个数与1取与结果不为1的话,就需要将这个数字右移1位,然后将indexOf1的大小乘2.
    public static int findFirstOf1(int num){
        if(num < 0){
            return  -1;
        }
        int indexOf1 = 1;
        while(num != 0){
            if((num & 1) == 1){
                return  indexOf1;
            }else{
                num = num >> 1;
                indexOf1 *= 2;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 5, 2, 1, 7};
        int[] res = findNumsAppearOnce(arr);
        for(int num : res){
            System.out.print(num + "  ");
        }
    }
}

总结

1、判断某个数x的第n位(如第3位)上是否为1,

    1)通过 x & 00000100 的结果是否为0 来判断。(不能根据是否等于1来判断)

    2)通过(x>>3)&1 是否为0 来判断

2、将一组数字分为两组,可以根据某位上是否为1来进行分组,即根据和1相与(&1)的结果来进行分组。

3、将某个数x右移m位,一定要写成 x=x>>m;而不能只写成 x>>m;这个语句

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值