基础算法面试题---异或运算

异或运算

异或是一个数学运算符,应用于逻辑运算,计算机符号为“eor”。

在二进制中,规则为:
1^0 = 1
1^1 = 0
0^0 = 0
也就是相同为0,不同为1,也可以理解为不带进位的二进制加法。

举例:

5 ^ 3 = 6

5二进制:0 1 0 1
3二进制:0 0 1 1
异或: 0 1 1 0 = 6

异或满足的规律

1、归零率:a ^ a = 0(自己异或自己结果为0)
2、恒等率:a ^ 0 = a(与0异或结果不变)
3、交换律:a ^ b = b ^ a
4、结合律:a ^ b ^ c = a ^ (b ^ c )

经典算法题

1、在不使用额外变量的前提下,完成两个数的交换。

public class Eor_01 {
    
    
    public static void main(String[] args) {
    
    

        int a = 1;
        int b = 2;
        //使用临时变量交换两个数
        tempSwap(a, b);
        //使用异或方式
        eorSwap(a, b);
    }

    private static void eorSwap(int a, int b) {
    
    
        System.out.println("eor交换前,a: " + a + ",b:" + b);
        a = a ^ b;
        // b = a ^ b 等于 b = a ^ b ^ b ,根据交换律得出 b = a ^ (b ^ b),再根据归零率得出 b = a ^ 0,再根据恒等率得出 b = a,因此这一步完成了b被赋值成a
        b = a ^ b;
        //a = a ^ b 等于 a = a ^ b ^ a , 同理a被赋值成了b,最终完成了两个数的交换。
        a = a ^ b;
        System.out.println("eor交换后,a: " + a + ",b:" + b);

    }

    private static void tempSwap(int a, int b) {
    
    
        System.out.println("temp交换前,a: " + a + ",b:" + b);
        int c = a;
        a = b;
        b = c;
        System.out.println("temp交换后,a: " + a + ",b:" + b);
    }
}

2、在一堆非空的整数数组中,只有一种数字出现了奇数次,其他数都出现了偶数次,找出这个奇数次的数字。

public class Eor_02 {
    
    

    public static void main(String[] args) {
    
    
        //只有4出现了奇数次,其他都出现了偶数次
        int[] nums = {
    
    1, 1, -2, 6, 4, 1, 1, 6, -2, 4, 4};

        //一般的方式,利用hash统计每个数出现的次数,然后找出奇数的数字。
        System.out.println(findOddNumberByHash(nums));

        //eor方式
        System.out.println(findOddNumberByEor(nums));

    }

    private static int findOddNumberByEor(int[] nums) {
    
    
        /**
         * 还是根据结合律、归零率、恒等率可以理解为:

         1 ^ 1 ^ 1 ^ 1 ^ -2 ^ -2 ^ 6 ^ 6 ^ 4 ^ 4 ^ 4

         拆分为:
          1 ^ 1 ^ 1 ^ 1 = 0
          -2 ^ -2 = 0
          6 ^ 6 = 0
          4 ^ 4 = 0

         0 ^ 0 ^ 0 ^ 0 = 0

         剩下最后一个4
         4 ^ 0 = 4
         */
        int oddNumber = 0;
        for (int i = 0; i < nums.length; i++) {
    
    
            oddNumber ^= nums[i];
        }
        return oddNumber;
    }

    private static int findOddNumberByHash(int[] nums) {
    
    
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
    
    
            int k = nums[i];
            map.put(k, map.getOrDefault(k, 0) + 1);
        }

        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
    
    
            if (entry.getValue() % 2 != 0) {
    
    
                return entry.getKey();
            }
        }
        return 0;
    }

}

3、在一个长度为11数组中,放入1-10个数字,每个数字均出现了一次,再额外随机放入一个1-10的重复数字,找出这个重复的数字。

例如:[1,2,3,4,5,6,7,8,9,10,1],重复数字为1,[1,2,3,4,5,6,7,8,9,10,4],重复数字为4。

思路与上一题类似,变相的找出奇数次的数字。

public class Eor_03 {
    
    

    static Random random = new Random();

    public static void main(String[] args) {
    
    

        int[] nums = new int[11];
        for (int i = 1; i < nums.length; i++) {
    
    
            nums[i - 1] = i;
        }

        int oneTimes = random.nextInt(nums.length - 1) + 1;

        System.out.println("重复出现的数:" + oneTimes);

        nums[nums.length - 1] = oneTimes;

        System.out.println("打乱前:" + Arrays.toString(nums));

		//打乱数组顺序,只是为了数组中数字随机的演示效果,与算法本身无关。
        shuffle(nums);

        System.out.println("打乱后:" + Arrays.toString(nums));

        findOneTimesNumber(nums);

        findOneTimesNumberByEor(nums);

    }

    private static void findOneTimesNumberByEor(int[] nums) {
    
    
        int k = 0;

        //先让数组中的每个数进行异或运算
        for (int i = 0; i < nums.length; i++) {
    
    
            k ^= nums[i];
        }

        //再把结果与 1-10 分别异或一次,最终得到的数就是重复出现的
        for (int i = 1; i < nums.length; i++) {
    
    
            k ^= i;
        }
        
        System.out.println("eor方式找出重复出现的数:" + k);
    }

    private static void findOneTimesNumber(int[] nums) {
    
    
        int s1 = 0;
        int s2 = 0;
        for (int i = 1; i < nums.length; i++) {
    
    
            s1 += i;
        }
        for (int i = 0; i < nums.length; i++) {
    
    
            s2 += nums[i];
        }
        System.out.println("累加方式找出重复出现的数:" + (s2 - s1));
    }

    private static void shuffle(int[] nums) {
    
    
        for (int i = 0; i < nums.length; i++) {
    
    
            int r = random.nextInt(nums.length - 1) + 1;
            int temp = nums[i];
            nums[i] = nums[r];
            nums[r] = temp;
        }
    }
}

4、一个数组中,有两种数出现了奇数次,其他都出现了偶数次,找出这两种数。

public class Eor_04 {
    
    
    public static void main(String[] args) {
    
    
        int[] nums = {
    
    1, 8, 8, 4, 2, 3, 1, 2, 2, 3};
        
        findTwoOddNumberByEor(nums);
    }

    private static void findTwoOddNumberByEor(int[] nums) {
    
    
        int k = 0;
        //先让所有数进行异或运算,得到的结果肯定是两个奇数异或的值,用变量k存储着。
        for (int i = 0; i < nums.length; i++) {
    
    
            k ^= nums[i];
        }
        
        /**
         *  从k中随便找一个二进制位上,下标为1的,k & (~k + 1)可以找到二进制上最右侧的1(只要找到下标有1的位置就可以了,哪一个无所谓,这里用个算法取巧,得到最右侧的1)。
         *  假设这个1在第2位,那么数组中就分为两类数,一类是第2位为1的,一类是第2位为0的。
         *	就等于把两个奇数拆分出来了。
         *
         * 验证,假设数组为:{1, 8, 8, 4, 2, 3, 1, 2, 2, 3}
         *
         * 分别计算二进制
         * 1:0001
         * 8:1000
         * 4:0100
         * 2:0010
         * 3:0011
         *
         * 两个奇数异或结果
         * 2 ^ 4 = 0110
         *
         * 0110最右侧的1在第2位,即得到:0010
         *
         * 第2位为1的数有 2,3
         * 第2位为0的数有 1,8,4
         *
         * 再让数组中所有的1,8,4异或,就得到了4
         * 再用4异或 2 ^ 4 就得到了 2
         *
         */
        int a = k & (~k + 1);

        int m = 0;
        for (int i = 0; i < nums.length; i++) {
    
    
            int num = nums[i];
            //如果 (num & a) != 0 则表示 第2位一定不为0
            if ((num & a) != 0) {
    
    
                m ^= num;
            }
        }

        int n = k ^ m;

        System.out.println("两个奇数分别为:" + m + "," + n);
    }
}

猜你喜欢

转载自blog.csdn.net/CSDN_WYL2016/article/details/113498207
今日推荐