当前位置: 首页 > news >正文

【算法笔记】异或运算

异或运算,指的是二进制的位运算

1、异或运算

  • 异或运算
  • 认识异或运算:二进制位相同为0,不同为1,可以记成无进位相加!
  • 异或运算的性质
  • 1、0^a == a a^a == 0
  • 2、异或运算满足交换律和结合律:
  • a^b = b^a
  • a(bc) = (ab)c

2、相关题目和应用

题目一:如何不用额外变量交换两个数

思路:根据异或运算的性质

  • 1、先将a的值设置为a^b
  • 2、再将b的值设置为ab,代入上一步的结果,b=abb,因为bb=0,则b=a
  • 3、再将a的值设置为ab,代入上面两步的结果,a=ab,b=a,则a=aba,所以a=b
  • 注意:
  • 异或方法交换两个数的本质是将当前的数的位置当成了临时变量,所以在第一步中,a=a^b,第二步中b=a
  • 所以代入第三步的时候因该是a=aba=a=b
/*** 题目一:如何不用额外变量交换两个数* 思路:根据异或运算的性质* 1、先将a的值设置为a^b* 2、再将b的值设置为a^b,代入上一步的结果,b=a^b^b,因为b^b=0,则b=a* 3、再将a的值设置为a^b,代入上面两步的结果,a=a^b,b=a,则a=a^b^a,所以a=b* 注意:* 异或方法交换两个数的本质是将当前的数的位置当成了临时变量,所以在第一步中,a=a^b,第二步中b=a* 所以代入第三步的时候因该是a=a^b^a=a=b*/public static void swap(int a, int b) {a = a ^ b;b = a ^ b;a = a ^ b;}/*** 用异或交换数组中的两个数*/public static void swap(int[] arr, int i, int j) {if (i == j) {// 异或方法交换两个数的本质是将当前的数的位置当成了临时变量// 所以,i和j不能是同一个位置,如果是同一个位置,会将当前位置的数变成0return;}arr[i] = arr[i] ^ arr[j];arr[j] = arr[i] ^ arr[j];arr[i] = arr[i] ^ arr[j];}

题目二:数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这种数

思路:

  • 根据异或运算的性质
  • 一个数和自己异或就变成了0,0和自己异或是其本身,
  • 可以得出一个结论:一个数和本身异或偶数次,就会变成0,一个数和本身异或奇数次,还是本身
  • 所以,将数组中的所有数异或起来,最后异或的结果就是出现了奇数次的数
    /*** 题目二:数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这种数* 思路:* 根据异或运算的性质* 一个数和自己异或就变成了0,0和自己异或是其本身,* 可以得出一个结论:一个数和本身异或偶数次,就会变成0,一个数和本身异或奇数次,还是本身* 所以,将数组中的所有数异或起来,最后异或的结果就是出现了奇数次的数*/public static int getOddTimeNum(int[] arr) {if (arr == null || arr.length == 0) {throw new IllegalArgumentException("数组不能为空");}int xor = 0;for (int i : arr) {xor ^= i;}return xor;}

题目三:获取一个数二进制中最右侧的1所对应的数

思路:

  • 最右侧的1对应的数指的是一个数转换成二进制以后,每个位上要么是1,要么是0,我们将这个二进制数的最右侧的1保留下来,其他的都设置为0,这个数就是最右侧的1对应的数
  • 例如:10100,最右侧的1对应的数就是00100
    过程:
  • 这个题的重点是如何将最右侧的1保留下来,其他的都设置为0,下面是思路
  • 1、将这个数取反,例如10100取反就是01011
  • 2、将按位取反后的数加1,这样就会产生进位,结果恰好是最右侧的1和原来数字最右侧1对应,产生进位的位置都变成了0,示例中01011+1=01100
  • 3、再将这个数和取反加一后的数字按位与,这样就会将最右侧的1保留下来,其他的都设置为0,例如 10100 & 01100 = 00100
  • 4、所以,我们可以通过num&(~num+1)来获取最右侧的1所对应的数,根据补码性质,一个数的相反数就是其反码加1,所以可以得出下面的结论
  • num&(~num+1) = num&(-num)
  • 5、所以,我们可以通过num&(-num)来获取最右侧的1所对应的数
/*** 题目三:获取一个数二进制中最右侧的1所对应的数* 思路:* 最右侧的1对应的数指的是一个数转换成二进制以后,每个位上要么是1,要么是0,我们将这个二进制数的最右侧的1保留下来,其他的都设置为0,这个数就是最右侧的1对应的数* 例如:10100,最右侧的1对应的数就是00100* 过程:* 这个题的重点是如何将最右侧的1保留下来,其他的都设置为0,下面是思路* 1、将这个数取反,例如10100取反就是01011* 2、将按位取反后的数加1,这样就会产生进位,结果恰好是最右侧的1和原来数字最右侧1对应,产生进位的位置都变成了0,示例中01011+1=01100* 3、再将这个数和取反加一后的数字按位与,这样就会将最右侧的1保留下来,其他的都设置为0,例如 10100 & 01100 = 00100* 4、所以,我们可以通过num&(~num+1)来获取最右侧的1所对应的数,根据补码性质,一个数的相反数就是其反码加1,所以可以得出下面的结论* num&(~num+1) = num&(-num)* 5、所以,我们可以通过num&(-num)来获取最右侧的1所对应的数*/public static int getFarRightOneTimeNum(int num) {return num & (-num);}
public static void main(String[] args) {System.out.println("-----测试数组中有一个数出现奇数次-----");System.out.println(getOddTimeNum(new int[]{1, 1, 3, 3, 5, 6, 6, 8, 8, 10, 10}));}

题目四:个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两种数

思路:

  • 题目二我们找到了一种出现奇数次的数,这个题目中是两种奇数次,比题目二更难一点
  • 1、结合题目二的思路,如果我们将所有的数都异或起来,最后异或后的结果就是 两个奇数次的数异或的结果,如果将这两种数设为a和b,则异或的结果xor=a^b,现在的问题是如何从异或结果中分开这两个数的问题。
  • 2、两个数如果同一个位置相同,异或后这个位置就会成为0,因此如果我们取出异或结果最右侧为1的数rightOne,代表a、b中该位置只有一个是1,另一个是0
  • 3、这个时候我们再将数组中该位置位1的数单独异或一次,得到的结果xor1,代表了a、b中最右侧位置为1的一个数
  • 4、再将xor和xor1进行异或,就可以得到另一个数
/*** 题目四:个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两种数* 思路:* 题目二我们找到了一种出现奇数次的数,这个题目中是两种奇数次,比题目二更难一点* 1、结合题目二的思路,如果我们将所有的数都异或起来,最后异或后的结果就是 两个奇数次的数异或的结果,如果将这两种数设为a和b,则异或的结果xor=a^b,现在的问题是如何从异或结果中分开这两个数的问题。* 2、两个数如果同一个位置相同,异或后这个位置就会成为0,因此如果我们取出异或结果最右侧为1的数rightOne,代表a、b中该位置只有一个是1,另一个是0* 3、这个时候我们再将数组中该位置位1的数单独异或一次,得到的结果xor1,代表了a、b中最右侧位置为1的一个数* 4、再将xor和xor1进行异或,就可以得到另一个数*/public static DoubleOddResult getDoubleOddTimeNum(int[] arr) {if (arr == null || arr.length == 0) {throw new IllegalArgumentException("数组不能为空");}// 首先得到xor=a^bint xor = 0;for (int i : arr) {xor ^= i;}// 得到最右侧的1所对应的数(题目三的结论)int rightOne = xor & (-xor);// 将数组中最右侧为1的数提取出来,单独异或,得到a或者bint xor1 = 0;for (int i : arr) {if ((i & rightOne) != 0) {xor1 ^= i;}}// 得到另一个数int xor2 = xor ^ xor1;return new DoubleOddResult(xor1, xor2);}public static void main(String[] args) {System.out.println("-----测试数组中有一个数出现奇数次-----");System.out.println(getOddTimeNum(new int[]{1, 1, 3, 3, 5, 6, 6, 8, 8, 10, 10}));System.out.println("-----测试数组中有两个数出现奇数次-----");DoubleOddResult result = getDoubleOddTimeNum(new int[]{1, 1, 3, 3, 5, 6, 6, 7, 8, 8, 10, 10});System.out.println(result.num1);System.out.println(result.num2);}static class DoubleOddResult {public DoubleOddResult(int num1, int num2) {this.num1 = num1;this.num2 = num2;}int num1;int num2;}

题目五:数组中所有的数都出现了M次,只有一种数出现了K次,且1 <= K < M(输入一定能够保证),返回出现K次的数

思路:

  • 这个题目最简单的办法就是用一个map,将每个数出现的次数统计出来,然后遍历map,找到出现次数为K的数,只是用了map,空间复杂度就会变高
  • 顺着这个思路,我们可以用和一个int占用同样大小(java中int是4个字节,32位)的空间的数组help,用help数组对应的下边来记录原来数中对应位置为1的个数
  • 因为其他数字都出现了M次,那对应位置为1的个数和一定是能被M整除的,如果不能被整除,说明出现K次的数在这个位置上为1,我们就把这个位置还原
  • 最后还原得到的数,就是出现了K次的数

代码和对数期测试方法如下:

import java.util.HashMap;
import java.util.HashSet;public class KM {/*** 题目五:数组中所有的数都出现了M次,只有一种数出现了K次,且1 <= K < M(输入一定能够保证),返回出现K次的数* 思路:* 这个题目最简单的办法就是用一个map,将每个数出现的次数统计出来,然后遍历map,找到出现次数为K的数,只是用了map,空间复杂度就会变高* 顺着这个思路,我们可以用和一个int占用同样大小(java中int是4个字节,32位)的空间的数组help,用help数组对应的下边来记录原来数中对应位置为1的个数* 因为其他数字都出现了M次,那对应位置为1的个数和一定是能被M整除的,如果不能被整除,说明出现K次的数在这个位置上为1,我们就把这个位置还原* 最后还原得到的数,就是出现了K次的数*/public static int km(int[] arr, int k, int m) {if (arr == null || arr.length == 0) {throw new IllegalArgumentException("数组不能为空");}int[] help = new int[32];// 用help数组对应下标的数字记录原数组中对应位置为1的个数,例如help[0]记录的是原数组中所有数的二进制表示中,第0位为1的个数for (int num : arr) {for (int i = 0; i < 32; i++) {// num >> i 代表将i位置的数移动到最右侧,然后 & 1,将其他的位置上的数字都置为0,只保留该位置的数help[i] += (num >> i) & 1;}}// 保存还原的结果的数字int result = 0;// 遍历help数组,将对应位置为1的个数不是M的倍数的位置,还原到result中for (int i = 0; i < 32; i++) {if (help[i] % m != 0) {// 说明出现K次的数在这个位置上为1,将这个位置上的数还原到result中result |= (1 << i);}}return result;}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 最大的多少种数int kinds = 5;// 用来限制k和m的最大值int max = 9;boolean succeed = true;for (int i = 0; i < testTime; i++) {int a = (int) (Math.random() * max) + 1; // a 1 ~ 9int b = (int) (Math.random() * max) + 1; // b 1 ~ 9int k = Math.min(a, b);int m = Math.max(a, b);// k < mif (k == m) {m++;}// 先生成一个随机的数组int[] arr = generateKMRandomArray(kinds, maxSize, k, m);int result1 = km(arr, k, m);int result2 = kmComparator(arr, k, m);
//            System.out.println("原数组:");
//            printArray(arr);
//            System.out.printf("k的值:%d,m的值:%d,km算法的结果:%d,比较器的结果:%d\n", k, m, result1, result2);if (result1 != result2) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.printf("k的值:%d,m的值:%d,km算法的结果:%d,比较器的结果:%d\n", k, m, result1, result2);break;}}System.out.println(succeed ? "successful!" : "error!");}/*** km问题的比较器,用来判断km算法的结果是否正确* 直接使用map统计每个数出现的次数,然后遍历map,找到出现次数为K的数*/public static int kmComparator(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);}}int ans = 0;for (int num : map.keySet()) {if (map.get(num) == k) {ans = num;break;}}return ans;}/*** 生成一个满足km问题的随机数组*/public static int[] generateKMRandomArray(int maxKinds, int maxSize, int k, int m) {// 一共有多少种数,最少要有出现k和m两种int numKinds = (int) (Math.random() * maxKinds) + 2;// 数组长度: k  + (numKinds - 1) * mint[] arr = new int[k + (numKinds - 1) * m];// 得到出现k次所对应的数int kTimeNum = randomNumber(maxSize);// 先把k的数字填进去int index = 0;for (; index < k; index++) {arr[index] = kTimeNum;}// 填入其他的数字numKinds--;// 用来验证生成的数字有没有用过HashSet<Integer> set = new HashSet<>();set.add(kTimeNum);while (numKinds != 0) {int curNum = 0;do {// 生成没有用过的数字curNum = randomNumber(maxSize);} while (set.contains(curNum));set.add(curNum);numKinds--;for (int i = 0; i < m; i++) {arr[index++] = curNum;}}// arr 填好了,随机交换一下位置for (int i = 0; i < arr.length; i++) {// i 位置的数,我想随机和j位置的数做交换int j = (int) (Math.random() * arr.length);// 0 ~ N-1int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}return arr;}/*** 生成一个满足[-maxSize, +maxSize]的数*/public static int randomNumber(int maxSize) {return (int) (Math.random() * (maxSize + 1)) - (int) (Math.random() * (maxSize + 1));}public static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}}

后记
个人学习总结笔记,不能保证非常详细,轻喷


文章转载自:

http://OY1hEdIG.pwmpn.cn
http://ZoXgqg3E.pwmpn.cn
http://PUjUi2GY.pwmpn.cn
http://O3FWguyr.pwmpn.cn
http://bVoXTUfh.pwmpn.cn
http://SI6if5Wf.pwmpn.cn
http://YoaIpzZ4.pwmpn.cn
http://LQQdBFmg.pwmpn.cn
http://M6OFlcTc.pwmpn.cn
http://4VkWRSRT.pwmpn.cn
http://mxVi702s.pwmpn.cn
http://HhhJJit3.pwmpn.cn
http://ngUUddXD.pwmpn.cn
http://f9YaaOeE.pwmpn.cn
http://T7WXUEqo.pwmpn.cn
http://fqLSYDWY.pwmpn.cn
http://cNJl6BDa.pwmpn.cn
http://HihG9GwP.pwmpn.cn
http://MYz6FtZe.pwmpn.cn
http://Wc4BxJSV.pwmpn.cn
http://oauJY8rY.pwmpn.cn
http://GhrUuECC.pwmpn.cn
http://HU9XorZY.pwmpn.cn
http://bx2HSS4k.pwmpn.cn
http://N0GwIFiZ.pwmpn.cn
http://QLt6irUz.pwmpn.cn
http://0Lg07bKW.pwmpn.cn
http://VCoSwsIg.pwmpn.cn
http://YVYAvfDy.pwmpn.cn
http://92ll0mcH.pwmpn.cn
http://www.dtcms.com/a/368896.html

相关文章:

  • 数据结构:排序
  • mac清除浏览器缓存,超实用的3款热门浏览器清除缓存教程
  • 残差连接与归一化结合应用
  • 【知识点讲解】模型扩展法则(Scaling Law)与计算最优模型全面解析:从入门到前沿
  • MySQL锁篇-锁类型
  • LINUX_Ubunto学习《2》_shell指令学习、gitee
  • FastGPT源码解析 Agent知识库管理维护使用详解
  • MATLAB 2023a深度学习工具箱全面解析:从CNN、RNN、GAN到YOLO与U-Net,涵盖模型解释、迁移学习、时间序列预测与图像生成的完整实战指南
  • 均匀圆形阵抗干扰MATLAB仿真实录与特点解读
  • 《深入理解双向链表:增删改查及销毁操作》
  • 属性关键字
  • 【Linux基础】Linux系统管理:MBR分区实践详细操作指南
  • 国产化FPGA开发板:2050-基于JFMK50T4(XC7A50T)的核心板
  • 时隔4年麒麟重新登场!华为这8.8英寸新「手机」给我看麻了
  • 敏感词过滤这么玩?自定义注解 + DFA 算法,优雅又高效!
  • RPC内核细节(转载)
  • 如何将 Android 设备的系统底层日志(如内核日志、系统服务日志等)拷贝到 Windows 本地
  • Vue美化文字链接(超链接)
  • 中囯移动电视盒子(魔百和)B860AV2.1-A2和CM311-5-zg刷机手记
  • TCP/IP函数——sendmsg
  • Linux网络自定义协议与序列化
  • 人工智能机器学习——聚类
  • docker-compose跨节点部署Elasticsearch 9.X集群
  • Qt控件:Item Views/Widgets
  • 轻量高效:Miniserve文件共享神器
  • Netty从0到1系列之JDK零拷贝技术
  • 从无图到轻图,大模型时代,图商的新角逐
  • 【物种分布模型】R语言物种气候生态位动态量化与分布特征模拟——气候生态位动态检验、质心转移可视化、适生区预测等
  • 盟接之桥说制造:在安全、确定与及时之间,构建品质、交期与反应速度的动态平衡
  • 【Android】SQLite使用——增删查改