算法题:找出一个数组中,出现次数为奇数次的数。详细解析,加个人理解。

数组中,出现奇数次的数

从一个数组中找出出现了奇数次的数字,要求:

  1. 时间复杂度:O(n)
  2. 空间复杂度:O(1)

题目

  1. 从数组中找出一个出现奇数次的数字,如:

    {1, 2, 2, 1, 3, 1, 1, 3, 3},结果返回: 3

  2. 从数组中找出两个出现奇数次的数字,如:

    {1, 2, 2, 1, 3, 1, 1, 3, 3, 4},结果返回: 3, 4

使用异或解题 (java)

  1. 定义一个int eor = 0;,将array中的数据遍历,与eor进行异或计算:

    int array = {1, 2, 2, 1, 1, 1, 3};
    int eor = 0;
    for (int cur: array) {
        eor = eor ^ cur;
    }
    

    解析:当cur = 1, eor = 0,eor与cur异或,将返回cur: 1。以此往下,偶数项将化为0。如下:

    1 ^ 2                   = 3
    1 ^ 2 ^ 1               = 2
    1 ^ 2 ^ 1 ^ 2           = 0
    1 ^ 2 ^ 1 ^ 2 ^ 1       = 1
    1 ^ 2 ^ 1 ^ 2 ^ 1 ^ 1   = 0
    

    只要这两个数在数组中为偶数,那一定会化为0,0再与任何数异或都是返回数的本身。因此:

    3 ^ 3 = 0
    3 ^ 3 ^ 3 = 3
    

    我们通过遍历,将每个数进行异或,去除掉出现偶数次的数。就可以获得奇数次的3,将它存储到eor

  2. 定义一个int eor = 0;,与第一题一致的代码。这样我们就会得到 eor = a ^ b, 其中的 a, b是两个出现奇数次的数字。eor遍历一次后,偶数次的数将会都变成0,只剩下奇数次的数,如:

    • 处理前:{1, 2, 2, 1, 3, 1, 1, 3, 3, 4}
    • 处理后:{3, 4}

    你问为什么{3, 3, 3}会变成3,因为:3 ^ 3 ^ 3 = 3,前面两个3将被化掉。剩下3, 4。

    已知 a ^ b,则a或b一定不为0,互不相等,且a ^ b的 二进制位上 为1的数,代表a, b这两位数上的二进制位是不一样的。如:

      A   =   0 0 1
      B   =   0 1 0
    A ^ B =   0 1 1
    

    从此看出,A,B有两处的二进制位不同,我们只需要 通过最后一位1来与之比对,即可找到a或b

    // eor = a ^ b
    int rightOne = eor & (~eor + 1);
    /**
     * 获取最右侧1
     * eg.
     *  eor     = 10101001 原码
     *  ~eor    = 01010110 反码
     *  ~eor+1  = 01010111 补码
     *  eor & (~eor + 1) = 
     *      10101001
     *  &   01010111
     *  ------------------
     *      00000001
     */
    

    但是,最后一位1的位置,其他数的二进制位也可能是1,这样就会导致误判。因此,我们可以再进行异或操作,筛选掉出现偶数次的数,留下奇数次的数。

    int onlyEor = 0;
    for (int cur: arr) {
        if ((cur & rightOne) == 0) {
            onlyEor = onlyEor ^ cur;
        }
    }
    /**
     * eg.
     *  cur      = 0010
     *  rightOne = 0001
     *  cur & rightOne = 0000
     *  则 cur 可能是a或者b
     */
    

    我们获取到了 a ^ b, 由通过 rightOne获取到onlyEor = a 或 b,那么如何取出a或b呢?这里又用到异或:

    a ^ b ^ a = b
    a ^ b ^ b = a
    

    不管onlyEor是a或者b,都不影响我们取出另一个数。则:
    System.out.println(onlyEor + ", " + (onlyEor ^ eor));

文尾

java实现

public class Main {
    public static void printOddNumber(int[] arr) {
        int eor = 0;
        for (int number: arr) {
            eor = eor ^ number;
        }
        System.out.println("Odd number: " + eor);
    }

    public static void printOddNumberTwo(int[] arr) {
        int eor = 0;
        for (int cur: arr) {
            eor = eor ^ cur;
        }
        // eor = a ^ b
        int rightOne = eor & (~eor + 1);
        /**
         * 获取最右侧1
         * eg.
         *  eor     = 10101001
         *  ~eor    = 01010110
         *  ~eor+1  = 01010111
         *  eor & (~eor + 1) = 
         *      10101001
         *  &   01010111
         *  ------------------
         *      00000001 
         */
        // System.out.println("rightOne: " + rightOne);

        int onlyEor = 0;
        for (int cur: arr) {
            if ((cur & rightOne) == 0) {
                onlyEor = onlyEor ^ cur;
            }
        }
        /**
         * eg. 
         *  cur = 01001011
         *  cur & rightOne =
         *      01001010
         * &    00000001
         * ----------------
         *      00000000
         *
         */

        /**
         * eor = a ^ b
         * onlyEor ^ eor = a 或 b
         */
        System.out.println(onlyEor + ", " + (onlyEor ^ eor));
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 1, 2, 1, 3, 1, 3, 3};
        printOddNumber(arr);
        int[] arr1 = {1, 2, 1, 2, 1, 1, 3, 3, 4, 3};
        printOddNumberTwo(arr1);
    }
}

信息

  • 作者:PYmili
  • 邮件:mc2005wj@163.com
  • Q群: 706128290
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Pymili

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值