2、异或运算

1、按位异或运算 ^

按位异或(^): 相同为0,不同为1;
按位异或(^): 相当于无进位相加
例如:01010 ^ 01101 = 00111
特性:

  • 任何数异或上0等于其本身: 0 ^ n = n
  • 两个相同的数异或为0: n ^ n = 0
  • 异或运算满足交换律,和结合律
    • 交换:a ^ b = b ^ a
    • 结合:(a ^ b) ^ c = a ^ (b ^ c)

3、常见 Coding

3.1、不使用额外变量交换两个数

注意:a,b 不能是同一个块内存,a,b不能指向同一个内存
示例:
a = 5,b = 6
a = 6,b = 5
步骤:

  • a = a ^ b
  • b = a ^ b,相当于 b = (a ^ b) ^ b = a
  • a = a ^ b,相当于 a = (a ^ b) ^ a = b

代码实现:

点击查看代码
public class XorSwap {
    public static void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

    public static void main(String[] args) {
        int a = 5;
        int b = 6;
        System.out.println("a=" + a + ", b=" + b);

        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        System.out.println("a=" + a + ", b=" + b);

        int[] arr = {3, 1, 100};
        int i = 0;
        int j = 0;

        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];

        System.out.println("arr[i]=" + arr[i] + ", arr[j]=" + arr[j]);
        // [0, 1, 100]
        System.out.println(Arrays.toString(arr));
    }
}

3.2、怎么把一个int类型的数,提取出最右侧的1来

操作: a 按位与 a 的相反数,a & (-a) = a & (~a + 1)
例:

a: 0010 1100
~a:1101 0011 // 取反
+1:1101 0100 // 取反加一,即 -a
a & (-a) = 0000 0100

3.3、数组arr[]中一种数出现了奇数次,其他都出现了偶数次,怎么找到并打印这种数?

分析: 异或相当于无进位相加,偶数个 1 相加为 0,奇数个 1 相加为 1

    public static void printOddTimesNum(int[] arr) {
        int eor = 0;
        int len = arr.length;
        for (int i = 0; i < len; i++) {
            eor ^= arr[i];
        }
        System.out.println(eor);
    }

3.4、数组arr[]中,有两种数,出现奇数次,其他出现偶数次,怎么找到并打印这两种数?

3.4.1、分析

  • 假设 a,b出现奇数次,[..a,..,b..]
  • 遍历异或arr 中所有数得到结果一定是: eor = a ^ b
  • 将 eor最右侧的 1提取出来,则可以将arr 分为两堆数据 [{a...},{b...}]
  • 遍历异或{a...} 这一堆数据结果即为: eor' = a
  • 所以 b = eor ^ eor' = a ^ b ^ a

3.4.2、代码实现

点击查看代码
public class EvenTimesOddTimes {
    /**
     * arr中,有两种数,出现奇数次,其他出现偶数次
     * 步骤:假设 a,b出现奇数次
     * 遍历异或所有数,得到结果 eor = a ^ b != 0
     *
     * @param arr
     */
    public static void printOddTimesNum2(int[] arr) {
        int eor = 0;
        int len = arr.length;
        for (int i = 0; i < len; i++) {
            eor ^= arr[i];
        }

        // 将eor 最右侧的 1 提取出来
        // 例: eor:  0011 0100
        // rightOne: 0000 0100
        int rightOne = eor & (-eor);

        // eor = a ^ b,则最右侧 1提取的数 rightOne, 就可以把 arr 分为两堆 [{a...},{b...}]
        // ero', 假设 a 的最右侧 1,与 rightOne 相等,则用 eor' 异或 {a...} 这一堆数其结果就是 eor' = a
        int eor_ = 0;
        for (int i = 0; i < len; i++) {
            if ((rightOne & arr[i]) != 0) {
                eor_ ^= arr[i];
            }
        }
        // eor = a ^ b,eor' = a,则 b = eor ^ eor'
        System.out.println("a=" + eor_ + " b=" + (eor ^ eor_));
    }

    public static void main(String[] args) {
        int[] arr = {3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1};
        printOddTimesNum(arr); // 2

        int[] arr1 = {3, 3, 2, 3, 1, 1, 5, 1, 3, 1, 1, 1};
        printOddTimesNum2(arr1); // a=5 b=2
    }
}

3.5、数组中所有的数都出现了M次,只有一种数出现了K次,返回出现了K次的数

  • 输入 arr[] 一定能保证
  • 数组中所有的数都出现了 M 次,只有一种数出现了 K 次
  • 且 1 <= K < M
  • 返回:出现了 K 次的数
  • 要求:额外空间复杂度 O(1),时间复杂度 O(N)

3.5.1、分析

  • int 类型的数 4 字节 = 32 位,申请一个临时数组 temp[32]
  • 将所有数的二进制位,出现 1 的次数保存到temp 中
    • temp[0] 0位置的 1 出现了几次
    • temp[i] i位置的 1 出现了几次
  • 因为 K < M,所以如果某一位的 1 出现的次数不是 M 的整数倍,那么出现 K 次的数在该位置上一定为 1

3.5.2、代码实现,三种不同的实现方式

点击查看代码
/**
 * 输入 arr[] 一定能保证
 * 数组中所有的数都出现了 M 次,只有一种数出现了 K 次
 * 且 1 <= K < M
 * 返回:出现了 K 次的数
 * 要求:额外空间复杂度 O(1),时间复杂度 O(N)
 *
 * @author smile
 */
public class KM {

    /**
     * 数组中所有的数都出现了 M 次,只有一种数出现了 K 次
     *
     * @param arr 数组
     * @param K   某个数出现了 k 次
     * @param M   出现了 m 次的数
     * @return 出现了 k 次的数
     */
    public static int onlyKTimes(int[] arr, int K, int M) {
        int[] temp = new int[32];

        // temp[0] 0位置的 1 出现了几次
        // temp[1] 1位置的 1 出现了几次
        for (int num : arr) {
            for (int i = 0; i <= 31; i++) {
                temp[i] += (num >> i) & 1;
            }
        }

        // K < M,如果某一位的 1 出现的次数不是 M 的整数倍,
        // 那么出现 K 次的数在该位置上一定为 1
        int res = 0;
        for (int i = 0; i < 32; i++) {
            if (temp[i] % M != 0) {
                res |= (1 << i);
            }
        }
        return res;
    }

    public static HashMap<Integer, Integer> maps = new HashMap<>();

    public static int onlyKtimes1(int[] arr, int k, int m) {
        if (maps.size() == 0) {
            mapCreate(maps);
        }

        int[] temp = new int[32];
        // temp[0] 0 位置的 1 出现了几次
        // temp[i] i 位置的 1 出现了几次
        for (int num : arr) {
            while (num != 0) {
                int rightOne = num & (-num);
                temp[maps.get(rightOne)]++;
                // 将 num 的最后一位 1 置 0
                num ^= rightOne;
            }
        }

        int res = 0;
        for (int i = 0; i < 32; i++) {
            if (temp[i] % m != 0) {
                res |= (1 << i);
            }
        }
        return res;
    }

    public static void mapCreate(HashMap<Integer, Integer> maps) {
        int value = 1;
        for (int i = 0; i < 32; i++) {
            maps.put(value, i);
            value <<= 1;
        }
    }

    // 测试,哈希表统计的形式
    public static int test(int[] arr, int K, int M) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int num : arr) {
            if (map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            } else {
                map.put(num, 1);
            }
        }

        for (int num : map.keySet()) {
            if (map.get(num) == K) {
                return num;
            }
        }
        return -1;
    }

    /**
     * 生成满足条件,所有的数都出现了 M 次,只有一种数出现了 K 次,的数组
     *
     * @param maxKinds 数组中最多有几种数
     * @param range    数组中每个数的范围 [-range, range]
     * @param k        出现了 k 次
     * @param m        出现了 m 次
     * @return 新数组
     */
    public static int[] generateRandomArray(int maxKinds, int range, int k, int m) {
        // 先生成出现了 k 次的数
        int kTimesNum = randomNumber(range);
        int times = k;

        // 数的种类,最少 2 种数
        int numKinds = (int) (Math.random() * maxKinds) + 2;

        // 数组长度: k * 1 + (numKinds - 1) * m
        int[] arr = new int[k + (numKinds - 1) * m];
        int index = 0;
        for (; index < times; index++) {
            arr[index] = kTimesNum;
        }

        // k 次的数加入之后,种类减 1
        numKinds--;
        HashSet<Integer> set = new HashSet<>();
        set.add(kTimesNum);

        // 设置出现 m 次的数
        while (numKinds != 0) {
            int curNum = 0;
            do {
                curNum = randomNumber(range);
            } while (set.contains(curNum));
            set.add(curNum);
            numKinds--;
            for (int i = 0; i < m; i++) {
                arr[index++] = curNum;
            }
        }

        // arr 已经生成,将其打乱
        int len = arr.length;
        for (int i = 0; i < len; i++) {
            // i 位置的数,随机和 j 位置的数交换
            // 0 ~ N - 1
            int j = (int) (Math.random() * len);
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        return arr;
    }

    /**
     * 生成 [-range, range] 范围的数
     *
     * @param range 整数
     * @return 整数
     */
    private static int randomNumber(int range) {
        return (int) (Math.random() * (range + 1)) - (int) (Math.random() * (range + 1));
    }


    public static void main(String[] args) {
        int[] arr = {1, 2, 2, 4, 2, 1, 1, 4};
        int k = 2;
        int m = 3;
        // 4
        System.out.println(onlyKTimes(arr, k, m));

        int[] arr1 = {1, 2, 2, -1, 2, 1, 1, -1};
        // -1
        System.out.println(onlyKTimes(arr1, k, m));

        // 测试 KM
        int kinds = 5;
        int range = 30;
        int testTimes = 500;
        int max = 9;
        System.out.println("测试开始");
        for (int i = 0; i < testTimes; i++) {
            // 1 ~ 9
            int a = (int) (Math.random() * max) + 1;
            int b = (int) (Math.random() * max) + 1;
            k = Math.min(a, b);
            m = Math.max(a, b);
            // 保证 k < m
            if (k == m) {
                m++;
            }

            int[] array = generateRandomArray(kinds, range, k, m);
            int res1 = test(array, k, m);
            int res2 = onlyKTimes(array, k, m);
            int res3 = onlyKtimes1(array, k, m);

            if (res1 != res2 || res2 != res3) {
                System.out.println("出错了!");
                System.out.println(res1);
                System.out.println(res2);
                System.out.println(res3);
                break;
            }
        }
        System.out.println("测试结束!");
    }
}
posted @   半之微笑  阅读(284)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
/*自定义皮肤*/
点击右上角即可分享
微信分享提示

目录导航

1、按位异或运算 ^
3、常见 Coding
3.1、不使用额外变量交换两个数
3.2、怎么把一个int类型的数,提取出最右侧的1来
3.3、数组arr[]中一种数出现了奇数次,其他都出现了偶数次,怎么找到并打印这种数?
3.4、数组arr[]中,有两种数,出现奇数次,其他出现偶数次,怎么找到并打印这两种数?
3.5、数组中所有的数都出现了M次,只有一种数出现了K次,返回出现了K次的数