异或运算的应用

一、基础知识

异或运算,相异为1。

异或运算是一种常用的位运算,在算法题中,对于避免额外的空间复杂度有独特的用处。

异或运算也被称为“无进位相加”,它具有以下特性:

特性1:0 ^ N = N

特性2:N ^ N = 0

特性3(交换律):a ^ b = b ^ a

特性4(结合律):(a ^ b) ^ c = a ^ (b ^ c)

特性3 和 4总结起来,就是同一批数,不论使用怎样的顺序,怎样的结合方式进行异或,其结果始终一样。

二、算法题

题目1:如何不使用额外空间交换两个数?

a = a ^ b;
b = a ^ b;
c = a ^ b;

推导:

a = a ^ b;
b = a ^ b = (a ^ b) ^ b = a;
a = a ^ b = (a ^ b) ^ a = b;

题目2:一个数组中有一种数出现了奇数次,其他数都出现了偶数次,找到并打印这个出现了奇数次的数。

public static int count(int[] arr) {int eor = 0;for (int i = 0; i < arr.length; i++) {eor ^= arr[i];}return eor;
}

解析:看到题目时,如果没有想到异或,可能会使用 HashMap 来进行词频统计,但这样就会开辟额外空间,而使用异或的方式可以轻松避免额外空间。这里用到了特性1和特性2。

题目3:如何把一个int数,最右侧1提取出来?例如:01101110010000,提取最右侧的 1 ,得到 0000000010000。

public static int mostRightOne(int num) {// 另外,num 的相反数是 -num,也等于 (~num) + 1。return num & ((~num) + 1);
}

解析:~num 代表 num 按位取反。~num + 1 不仅可以得到某整数最右边的1,同时,它也代表 -num,例如,若 num = 7,那么~num + 1 = -7。因此上面代码也可以写成 num & (-num)。

题目4:一个数组中有两种数出现奇数次,其他数都出现了偶数次,怎么找到并打印这两种数。

分析:有两种数出现了奇数次,那么 a != b ,用 eor 变量遍历异或整个数组,那么最后结果一定是 a ^ b。且 a!= b,那么 a^b != 0,那么 eor 就一定会在某个位置上是 1 。可以利用题目三中最右1的方法得到一个最右1。假设 eor 右边第三位是 1,那么就代表 a 的右第三位和 b 的右第三位一定是不一样的。那么数组中的所有的数就可以分成两类:第3位是1的数、第3位是0的数。那么a 和 b 一定是分开在这两类里的。

而由于整个数组异或的结果是 a ^ b ,整个数组又可以分为以 a 和 b 为代表的第3位是1的数、第3位是0的数 两类数,那么在各自的一类中,除了a 、b 之外,其他数一定是出现偶数次。那么就可以是以“第3位 是1的数”为条件,将数组重新异或一遍得到 eor1,假设 a 就属于 “第3位是1的数”这一类,那么eor1 = a,最后,eor ^ eor1 = b。

public static void printOddNum2(int[] arr) {int eor = 0;// eor = a ^ b != 0for (int i = 0; i < arr.length; i++) {eor ^= arr[i];}// eor 最右1int mostRightOne = eor & (-eor);int eor1 = 0;for (int i = 0; i < arr.length; i++) {if ((arr[i] & mostRightOne) != 0) {eor1 ^= arr[i];}}// eor1 = aint a = eor1;int b = eor ^ eor1;System.out.println("a = " + a + ", b = " + b);
}

题目5:一个数组中有一种数出现了K次,其他数出现了M次,M>1,  K < M ,找到出现 K 次的数,要求额外空间复杂度是O(1),空间复杂度O(N)。

解析:看到 “额外空间复杂度是 O(1)” ,基本上就可以把哈希表的方式排除了,最好的实现方向就是使用异或。解题的关键思路是 K < M 这个条件,如果数组中的每个数都表示为一个2进制数列,那么如果将所有数按位累加(注意是按位累加,不是按位相加),那么每个二进制位置上要么是M的整数倍,要么是M的整数倍加K。因此,将累加结果按位取余就可以得到该出现K次的数,例如:0K00KK00KKK0K00,最后因为不是求K,只要求返回该数,因此K= 1也无妨。

public static int KM(int[] arr, int K, int M) {int[] bit = new int[32];for (int num : arr) {for (int i = 0; i < bit.length; i++) {// 注意这里一定不能写成:num & (1 << i),比较一下两者的值就知道原因了bit[i] += (num >> i) & 1;}}int ans = 0;for (int i = 0; i < bit.length; i++) {if ((bit[i] % M) != 0) {ans |= (1 << i);}}return ans;
}

第五题对数器:

public class KMChecker {public static int test(int[] arr, int k, int m) {HashMap<Integer, Integer> countMap = new HashMap<>();for (int i : arr) {if (countMap.containsKey(i)) {countMap.put(i, countMap.get(i) + 1);} else {countMap.put(i, 1);}}for (Integer key : countMap.keySet()) {if (countMap.get(key) == k) {return key;}}return -1;}/*** [-range, +range]** @param range* @return*/public static int randomNum(int range) {return ((int) ((Math.random() * range) + 1) - (int) ((Math.random() * range) + 1));}public static int[] randomArr(int kinds, int range, int k, int m) {int kTimeNum = randomNum(range);// numKinds >= 2int numKinds = (int) (Math.random() * kinds) + 2;// k * 1 + (numKinds - 1) * mint[] arr = new int[k + (numKinds - 1) * m];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 = randomNum(range);} 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);int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}return arr;}public static void main(String[] args) {int kinds = 10;int range = 200;int testTime = 100000;int max = 9;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 = randomArr(kinds, range, k, m);// 对照组int ans1 = test(arr, k, m);// 测试方法int ans2 = 异或运算.KM(arr, k, m);if (ans1 != ans2)System.out.println("错误!");}}}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

链表的基本操作——反转与删除

引言 链表相关的问题几乎都是coding问题&#xff0c;以下是两个简单的链表问题。 一、单链表或双链表如何反转 1.1 单链表的反转操作 给定一个 Node 结构&#xff1a; public static class Node {public int value;public Node next;public Node(int data) {this.value d…

单向队列、双端队列、栈的模型实现

引言 自己实现简单的队列、栈的逻辑结构。 队列都包含头和尾两个指针&#xff0c;简单的单向队列只能在一端&#xff08;如&#xff1a;head端&#xff09;入列&#xff0c;在另一端&#xff08;如&#xff1a;tail 端&#xff09;出列&#xff1b;双端队列可以在 head 进出&…

递归算法及其时间复杂度分析

引言 “递归” 一词是比较专业的计算机术语&#xff0c;在现实生活中&#xff0c;有一个更可爱的词——“套娃”。如果把“递归算法”叫做“套娃算法”&#xff0c;或许可以减少一些恐惧程度。 套娃是有限的&#xff0c;同样&#xff0c;递归也是有限的&#xff0c;这和我们经…

算法设计中的基础常用代码

引言 本篇博客旨在记录一些基础算法知识的常见组合用法&#xff0c;以及何时使用&#xff0c;需要注意的问题等&#xff0c;长期更新。 为什么要这样总结呢&#xff1f;难道掌握了位运算、常用算法工具API的定义还不够吗&#xff1f; 这是因为某些知识比如 &、 |、 ~、 …

Redis —— 常用命令一览

引言 参考《菜鸟教程 Redis 常用命令》&#xff0c;其中红色为极其重要&#xff0c;蓝色为重要。 一、总览 二、key相关命令 三、String 相关命令 四、Hash 相关命令 五、List 相关命令 六、Set 相关命令 七、ZSet 相关命令

Redis 实用技术——Pipeline

引言 Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。 这意味着通常情况下一个请求会遵循以下步骤&#xff1a; 客户端向服务端发送一个查询请求&#xff0c;并监听Socket返回&#xff0c;通常是以阻塞模式&#xff0c;等待服务端响应。服务端处理命令&#x…

Redis 实用技术——消息发布和订阅

引言 发布订阅模型是redis的重要功能&#xff0c;它可以像网站动态一样&#xff0c;将消息发送到多个订阅者的主页里。 一、常用命令 二、消息格式 消息是一个有三个元素的多块响应&#xff1a; 如上图&#xff0c;发布者向 mysub 频道发送了一条消息&#xff0c;redis会返回…

Redis 实用技术——事务

引言 redis的事务不像关系型数据库的事务那样完整。 “快”是redis的特征&#xff0c;在事务管理的过程中&#xff0c;使用muti命令开启事务块&#xff0c;当输入多条命令后&#xff0c;再使用exec命令执行事务块中的全部命令。 Redis事务可以保证两件事&#xff1a; 1、隔…

排序算法——归并排序的相关问题

一、小和问题 问题描述&#xff0c;给定一个数组&#xff0c;如[1, 3, 2, 6, 5]&#xff0c;计算每个数左边小于自己的所有数的和&#xff0c;并累加。例如&#xff1a; 1左边没有数 3左边有一个小于自己的数 1 2左边有一个小于自己的数 1 6左边有三个小于自己的数 1 3 2 6…

排序算法——随机快速排序

引言 随机快排是一个非常有意思的排序排序算法&#xff0c;它的算法思想用到了如递归、荷兰国旗问题等诸多元素&#xff0c;还意外的引入了随机性的概念。 以下将逐步总结三个版本的快速排序&#xff0c;由浅入深总结快速排序的经典实现过程。 荷兰国旗问题参考&#xff1a;…

Redis 基础——五大类型与数据结构

引言 Redis 区分于 memcahced 的一个重要不同就是它具有明确的类型概念&#xff0c;在Redis 的使用过程中&#xff0c;都离不开这些类型的学习&#xff0c;它不仅是 Redis 能力的基础&#xff0c;同时也是一些重要数据结构和算法思想的体现。 本博客总结了五大类型的书面重点…

比较器的使用

一、TreeMap 实现排序 TreeMap是一个有序结构&#xff0c;TreeSet也是类似。 他们可以实现对元素的排序。TreeMap 是针对 key进行排序。 如果TreeMap的key 是Integer类型&#xff0c;可以无需指定任何特殊条件&#xff0c;默认即按照升序进行排序&#xff0c;如&#xff1a;…

经典数据结构——堆的实现

一、完全二叉树 堆是一种完全二叉树&#xff0c;什么是完全二叉树&#xff1f; 简单的说&#xff0c;一棵满二叉树表示的是所有节点全部饱和&#xff0c;最后一层全部占满&#xff1a; 而完全二叉树指的是满二叉树的最后一层&#xff0c;所有叶子节点都从左往顺序排满&#x…

排序算法 —— 堆排序

引言 此文基于《经典数据结构——堆的实现》中堆结构&#xff0c;实现一个以堆处理排序的算法。 一、算法思想 基于堆结构的堆排序的算法思想非常简单&#xff0c;循环获取大根堆中的最大值&#xff08;0位置的根节点&#xff09;放到堆的末尾&#xff0c;直到将堆拿空。 由…

经典数据结构——前缀树

引言 前缀树——trie /ˈtraɪ//树&#xff0c;也叫作“单词查找树”、“字典树”。 它属于多叉树结构&#xff0c;典型应用场景是统计、保存大量的字符串&#xff0c;经常被搜索引擎系统用于文本词频统计。它的优点是利用字符串的公共前缀来减少查找时间&#xff0c;最大限度…

排序算法 —— 计数排序

引言 计数排序是桶排序思想的一种具体实现&#xff0c;针对一些具有特殊限制的样本数据&#xff0c;如公司员工年龄&#xff0c;那么样本数据本身就一定在0~200之间&#xff0c;针对这样的数据&#xff0c;使用从0到200 的桶数组&#xff0c;桶的位置已经是有序的&#xff0c;…

Java多线程 —— 线程状态迁移

引言 线程状态迁移&#xff0c;又常被称作线程的生命周期&#xff0c;指的是线程从创建到终结需要经历哪些状态&#xff0c;什么情况下会出现哪些状态。 线程的状态直接关系着并发编程的各种问题&#xff0c;本文就线程的状态迁移做一初步探讨&#xff0c;并总结在何种情况下…

Java中的Unsafe

Java和C语言的一个重要区别就是Java中我们无法直接操作一块内存区域&#xff0c;不能像C中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C手动管理内存的能力。 Unsafe类&#xff0c;全限定名是sun.misc.Unsafe&#xff0c;从名字中我们可以看出来这个类对…

Java 写时复制容器 —— CopyOnWriteArrayList

引言 写时复制的含义是当容器发生修改操作时&#xff0c;如add() 等&#xff0c;就会将原来的容器整体复制一份&#xff0c;这个过程是加锁的。而如果只是读取资源&#xff0c;例如 get() &#xff0c;就不会受到任何同步要求的限制。 写时复制的理念是&#xff0c;如果多个读…

arm中断保护和恢复_浅谈ARM处理器的七种异常处理

昨天的文章&#xff0c;我们谈了ARM处理器的七种运行模式&#xff0c;分别是&#xff1a;用户模式User(usr)&#xff0c;系统模式System(sys)&#xff0c;快速中断模式(fiq)&#xff0c;管理模式Supervisor(svc)&#xff0c;外部中断模式(irq)&#xff0c;数据访问中止模式Abor…