先给出两道面试的算法题。
- 一种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
- 两种种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
这两道题的解法有很多,但是最快最简单的应该就是异或运算。
接下来我们需要了解什么是异或运算?
异或运算
异或(xor)是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:
a⊕b = (¬a ∧ b) ∨ (a ∧¬b)
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1表示真,0表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法。
总结一下,异或运算就是相同为0,不同为1。且A ^ A = 0 A ^ 0 = A 。同时满足交换律和结合律。
接下来开始解决我们之前提出来的两道算法题目。
题目一
一种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
思路
- 由于它指明了空间复杂度为O(1),所以改题目不能额外创建数组,只能额外创建一些变量。
- 由上面的异或运算可知,
A ^ A = 0 A ^ 0 = A
。而改题目中恰好只有一个数字出现了奇数次。所以偶数次的数字直接消为0,最后只有奇数次的数字。
代码演示
/**
* @version v1.0
* @ProjectName: 数据结构
* @ClassName: demo01
* @Description: 一种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
* 两种种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
* @Author: 赵先生
* @Date: 2022/2/9 10:25
*/
public class demo01 {
/**
* 一种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
* A ^ A = 0 A ^ 0 = A 满足交换律和结合律
* @param arr
*/
public static void printOddTimesNum1 (int[] arr) {
int eor = 0;
for (int cur : arr) {
eor = cur ^ eor;
}
System.out.println(eor);
}
}
题目二
两种种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
思路
- 我们先整体异或运算,这样子得要的是一个数是a ^ b,这里的ab是指为奇数次的这两个数。
- 然后我们提取eor中最右边的值,因为这个值就代表了a和b不同的那一位。(固定写法)
- 我们通过这个数字就可以将a和b分开,然后得要a或者b
- 最后通过得到的数和eor进行异或运算得到另外一位。
代码演示
/**
* @version v1.0
* @ProjectName: 数据结构
* @ClassName: demo01
* @Description: 一种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
* 两种种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
* @Author: 赵先生
* @Date: 2022/2/9 10:25
*/
public class demo01 {
/**
* 两种种数出现了奇数次,其他数出现了偶数次,要求找到这个数,且时间复杂度为O(n),空间复杂度为O(1)
* 思路:
* 1,我们先整体异或运算,这样子得要的是一个数是a ^ b,这里的ab是指为奇数次的这两个数。
* 2,然后我们提取eor中最右边的值,因为这个值就代表了a和b不同的那一位。
* 3,我们通过这个数字就可以将a和b分开,然后得要a或者b
* 4,最后通过得到的数和eor进行异或运算得到另外一位。
* @param arr
*/
public static void printOddTimesNum2(int[] arr) {
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
//eor = a ^ b(这里的a和b表示的是为奇数的这两个数)
//eor != 0
//eor必然有一个位置上是1,这个位置也就是a和b不相同的一个位置
/**
* 提取出最右边的1
* 原理如下:
* A 10000111
* ~A 01111000
* ~A+1 01111001
* A & (~A+1) 00000001
*/
int rightOne = eor & (~eor + 1);
int onlyOne = 0;
//通过下面的for循环我们就知道了a或者b的一个数,再通过这个数异或出另外一个数
for (int cur : arr) {
if ((cur & rightOne) == 1) {
onlyOne ^= cur;
}
}
System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
}