数组arr中有2种数a和b出现了奇数次,其余数出现了偶数次,请你找到出现奇数次的ab,并打印他们
提示:异或运算重要知识点系列文章
这道题是互联网大厂面试高频题
只要了解异或运算的根本知识点,解决这些问题也就不在话下。
(1)认识异或运算的本质与基本规律,0^x=x,x异或x=0,有交换律,结合律,与顺序无关
(2)数组arr中,有一个数k出现了奇数次,其他数出现偶数次,请找到并打印这个数k
(3)如何把一个数字x最右侧那个1拿出来,变成00…10…的格式
(4)如何统计一个数字x中1的个数
题目
数组arr中有2种数a和b出现了奇数次,其余数出现了偶数次,请你找到出现奇数次的ab,并打印他们
一、审题
示例:arr = 1 1 1 2 2 2 3 3 4 4 4 4
显然a=1,b=2,他们出现了3次
其余的都出现了偶数次
故打印1 2
二、解题
暴力解
显然,你可以暴力统计词频,还是老方法,o(n)复杂度,但是速度贼慢。
异或运算最优解
同样是o(n)复杂度,但是速度贼快
因为位运算比四则运算速度快多了。
之前我们见过这个题:
【上面开头系列文章中的】(2)数组arr中,有一个数k出现了奇数次,其他数出现偶数次,请找到并打印这个数k
那是1个数x,特别好找
现在有2个数a和b,就不那么容易了,咱们要分析他们的特点
要知道,如果a和b都出现了奇数次,在arr所有元素异或之后,肯定能剩下来,也就是
eor = a^b,它还不会为0
你比如上面的案例:arr = 1 1 1 2 2 2 3 3 4 4 4 4
好,我们观察一下,既然1^2它不是0,显然有特点的
最起码a的最右侧那个1,它这个位置是1,那b的这个位置绝对不能是1,否则就麻烦了,你想想。
也就是说:a后面那些都是0,最右侧那个1左边所有的位置,是0是1无所谓,比如t
b后面那些是y随意,与a最右侧那个1对应的位置,必须是0,其他随意x
这样子的话,a^b,它才能保证不是0!
恰好我们利用这个特性,从新去arr中把a捞出来,
(1)刚刚我们不是已经找到eor=a^b了吗
(2)显然取eor最右侧那个1,rightOne=eor&(eor取反+1)【这个知识点x取反加1再与x拿到x最右侧那个1,在开头介绍的文章中有说过的哦】
(3)不妨设:令a=0,去arr中一次遍历,a=a^arr[i],一旦遇到a&rightOne != 0,则a找到了,a就是此时的arr[i]
啥意思?【好好理解这里!!!】
在a不断异或[i]的过程中,实际上,偶数次那些个数都废了,我们恰好能找到奇数次那个数,且最右侧是1那个数。
(4)此时a找到了,自然b也就就能搞定了,b=a^eor
为啥呢?eor=a^b
那你b自然等于a^eor=a异或a异或b=b【x异或x=0,x异或0=x】【这都是文章开头那些文章中讲的基础知识点】
现在,ab找到了吧!!!
是不是很妙!!
手撕代码:
public static void printOdd2TimesNumReview(int[] arr){
int N = arr.length;
//利用异或特性--先取a^b
int eor = 0;
for (int i = 0; i < N; i++) {
eor ^= arr[i];
}
//然后找eor最右侧那个1
int rightOne = eor & ((~eor) + 1);
//然后把a捞出来
int a = 0;
for (int i = 0; i < N; i++) {
//先看arri是否最右侧1存在,再比较
if ((rightOne & arr[i]) != 0) a ^= arr[i];
//反正要把所有那个位置为1的数都拿来异或,自然留下了a,b在这个位置它不可能是1
}
//找到了a,再求b
int b = a ^ eor;
System.out.println("a:"+ a +" b:"+ b);
}
public static void test(){
int[] arr = {1,1,1,2,2,2,3,3,4,4,4,4};
printOdd2TimesNum(arr);
printOdd2TimesNumReview(arr);
}
public static void main(String[] args) {
test();
}
思想明白了,代码非常简单!!!
总结
提示:重要经验:
1)异或的强大之处再次见识到了吧!!解决出现奇数次的问题
2)到此我们关于异或运算的知识点到一段路,后续有机会我们再来温习!