所谓异或运算就是:相同为0,不同为1【可以理解为无进位相加】
异或的常用公式:
异或是满足交换律和结合律的
在实际编程过程中,我们如果可以很好的使用异或操作,将大大提升我们算法的执行速度,大大降低时间复杂度与空间复杂度
public class Test01 {
public static void main(String[] args) {
int a = 10;
int b = 1;
a = a ^ b;
b = a ^ b; // (a ^ b) ^ b = a
a = a ^ b; // (a ^ b) ^ a = b
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
题目:一个数出现了奇数次,其他都出现了偶数次,找到出现奇数次的数
根据异或的特性,N^N=0;并且0异或N=N,因此,只要一直异或下去就能找到最后的奇数
public class Test01 {
public static void main(String[] args) {
int[] arr = {1,2,5,2,1,6,6,1,2,2,1};
int num = findOddNum(arr);
System.out.println(num);
}
public static int findOddNum(int[] arr){
int eor = 0;
for(int num : arr){
eor ^= num;
}
return eor;
}
}
首先,我们需要先学会如何将一个int数的最右侧的1提取出来
方法:取反加1【取相反数;相当于把右侧的1全部打散再加1来聚合】
// eor最右侧的1,提取出来
// eor : 00110010110111000
// rightOne :00000000000001000
题目: 一个数中有两个数字出现了奇数次,其他为偶数次,找到这两个数
假设这两个出现奇数次的数为a、b
首先,遍历数组,进行异或,最终得到结果为:a^b = eor
然后,取出a^b最右侧的1,可知在该位置a与b一个为1一个为0
遍历数组,找到最右侧也为1的数字,然后与eor(a^b)异或
最终可以得到a【两两异或为0,如果还剩下一个,那么一定为a】
最后a与eor异或(a ^ (a^b)),得到b
代码实现:
public class Test01 {
public static void main(String[] args) {
int[] arr = {2,3,2,1,5,5,7,6,6,7};
findOddNum2(arr);//odd1:3 odd2:1
}
public static void findOddNum2(int[] arr){
int eor = 0;
for (int num : arr) {
eor ^= num;
}
int rightOne = eor & (-eor);//获取到eor最右侧的1【可a与b在该位置上不相同】
int odd1 = 0;//eor'
for (int i = 0; i < arr.length; i++) {
// arr[1] = 111100011110000
// rightOne= 000000000010000
if((arr[i] & rightOne) != 0){
odd1 ^= arr[i];
}
}
System.out.println("odd1:" + odd1 + " " + "odd2:" + (eor ^ odd1));
}
}
一个数组中有一种数出现K次,其他数出现了M次数;M > 1, K < M
找到:出现了K次的数
要求:额外空间复杂度为O(1),时间复杂度为O(N)
思路:
/**
* 出现k次的数
* @param arr
* @param k
* @param m
* @return
*/
public static int onlyKTimes(int[] arr, int k, int m){
int[] t = new int[32];
//t[0] 0位置上的1出现了几个
//t[i] i位置上的1出现了几个
for (int num : arr) {
for (int i = 0; i <= 31; i++) {
t[i] += (num >> i) & 1;
}
}
int ans = 0;
//如果这个出现了K次的数就是0
//那么下面代码中的ans |= (1 << i)就不会发生
//那么ans就会一直维持0,最后返回0,也是正确的
for (int i = 0; i < 32; i++) {
if(t[i] % m == 0){
continue;
}
if(t[i] % m == k){
ans |= (1 << i);
} else {
return -1;
}
}
return ans;
}
用来构建对数器【使用结果正确,但是时间、空间复杂度不那么好的算法来当样板】
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);
}
}
int ans = 0;
for(int num : map.keySet()){
if(map.get(num) == k){
ans = num;
break;
}
}
return ans;
}
//构建对数器
/**
* 随机生成数组
* @param maxKinds 最多有几种数
* @param range 数的范围
* @param k k次
* @param m m次
* @return
*/
public static int[] randomArray(int maxKinds, int range, int k, int m){
//真命天子[出现k次的数]
int kTimeNum = randomNumber(range);
//真命天子出现的次数
int times = k;
int numKinds = (int)(Math.random() * maxKinds) + 2;//保证至少有两种数
int[] arr = new int[times + (numKinds - 1) * m];//数组总长度:1个数k次 + (numKinds - 1)个数m次
int index = 0;
//依次填充出现k次的数
for(; index < times; index++){
arr[index] = kTimeNum;
}
numKinds--;
HashSet<Integer> set = new HashSet<>();
set.add(kTimeNum);
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填充完成
//打乱数组中数字顺序
for (int i = 0; i < arr.length; i++) {
//i位置的数,随机与j位置上的数交换
int j = (int)(Math.random() * arr.length);// 0 ~ N-1
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
}
//随机生成[-range, range]的数
public static int randomNumber(int range){
return (int)(Math.random() * (range+1)) - (int)(Math.random() * (range+1));
}
package com.ali.math;
import java.util.HashMap;
import java.util.HashSet;
public class Test01 {
public static HashMap<Integer, Integer> map = new HashMap<>();
// 为了测试
public static void main(String[] args) {
int kinds = 5;
int range = 30;
int testTime = 100000;
int max = 9;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int a = (int) (Math.random() * max) + 1; // a 1 ~ 9
int b = (int) (Math.random() * max) + 1; // b 1 ~ 9
int k = Math.min(a, b);
int m = Math.max(a, b);
// k < m
if (k == m) {
m++;
}
int[] arr = randomArray(kinds, range, k, m);
int ans1 = test(arr, k, m);
int ans2 = onlyKTimes(arr, k, m);
if (ans1 != ans2) {
System.out.println(ans1);
System.out.println("出错了!");
}
}
System.out.println("测试结束");
}
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);
}
}
int ans = 0;
for(int num : map.keySet()){
if(map.get(num) == k){
ans = num;
break;
}
}
return ans;
}
/**
* 出现k次的数
* @param arr
* @param k
* @param m
* @return
*/
public static int onlyKTimes(int[] arr, int k, int m){
int[] t = new int[32];
//t[0] 0位置上的1出现了几个
//t[i] i位置上的1出现了几个
for (int num : arr) {
for (int i = 0; i <= 31; i++) {
t[i] += (num >> i) & 1;
}
}
int ans = 0;
//如果这个出现了K次的数就是0
//那么下面代码中的ans |= (1 << i)就不会发生
//那么ans就会一直维持0,最后返回0,也是正确的
for (int i = 0; i < 32; i++) {
if(t[i] % m == 0){
continue;
}
if(t[i] % m == k){
ans |= (1 << i);
} else {
return -1;
}
}
return ans;
}
//构建对数器
/**
* 随机生成数组
* @param maxKinds 最多有几种数
* @param range 数的范围
* @param k k次
* @param m m次
* @return
*/
public static int[] randomArray(int maxKinds, int range, int k, int m){
//真命天子[出现k次的数]
int kTimeNum = randomNumber(range);
//真命天子出现的次数
int times = k;
int numKinds = (int)(Math.random() * maxKinds) + 2;//保证至少有两种数
int[] arr = new int[times + (numKinds - 1) * m];//数组总长度:1个数k次 + (numKinds - 1)个数m次
int index = 0;
//依次填充出现k次的数
for(; index < times; index++){
arr[index] = kTimeNum;
}
numKinds--;
HashSet<Integer> set = new HashSet<>();
set.add(kTimeNum);
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填充完成
//打乱数组中数字顺序
for (int i = 0; i < arr.length; i++) {
//i位置的数,随机与j位置上的数交换
int j = (int)(Math.random() * arr.length);// 0 ~ N-1
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
}
//随机生成[-range, range]的数
public static int randomNumber(int range){
return (int)(Math.random() * (range+1)) - (int)(Math.random() * (range+1));
}
}