引言
本篇博文中核心介绍的是一些java很精辟的运算符操作,包含一些独特的思维。在面试的过程中,也可能会遇到这些问题。笔者在阿里巴巴的电话面试过程中就遇到这样的一个问题。分享给大家。
题目
给出一组整形(int)数组,在这个数组中只有一个数字是单独的,其它的数字都出现了2次,或者更多次。当然出现的次数全部统一,要么全部出现2次,要么全部出现多次。
分析一
最简单的方法,也是最容易想到的就是先对这个数组进行排序,然后相邻的进行比较,从而找出只出现一次的数字。换句话说,也就是如果存在一个数字,它和前面的数不相同和后面的数不相同,那么它就只出现一次。
这种处理方法,重点就转向了如何进行排序,排序的时间复杂度直接影响最终的结果。在以往的博客中提到过,在排序中快速排序和堆排序是时间复杂度最低的排序方式,那么我们可以采用快速排序来解决这个问题。但是,这不是本篇博文的核心,需要的同学可以自行查找排序方式,或者去博主以往的博客中查找。
实现代码一
package com.brickworkers;
import java.util.Arrays;
/** * * @author Brickworker * Date:2017年6月28日下午2:25:55 * 关于类Example.java的描述:用最简单的方法寻找数组中出现一次的值 * Copyright (c) 2017, brcikworker All Rights Reserved. */
public class Example {
public static int getSingle(int[] a){
//首先对数组进行排序
//排序不是本篇博文核心,我们直接调用Arrays库来解决排序,但是真正处理的时候用快速排序或者堆排序
Arrays.sort(a);
for (int i = 1; i < a.length - 1; i++) {//直接从1开始,可以两头判断
if(a[i] != a[i-1]){//把当前数字和它前面数字比较
//如果不相等,那么在与它后面的数字比较
if(a[i] != a[i+1]){
//那么可以断定已经找到了那个唯一的数字
return a[i];
}
}
}
return Integer.MIN_VALUE;//非法的时候抛出
}
public static void main(String[] args) {
//除了唯一出现的其他出现2次
int[] a = {1,2,3,4,5,6,7,8,9,1,2,4,5,6,7,8,9};
System.out.println("唯一存在的值为" + getSingle(a));
//除了唯一出现的,其他出现了3次
int[] b = {1,2,3,4,5,6,7,8,9,1,2,4,5,6,7,8,9,1,2,4,5,6,7,8,9};
System.out.println("唯一存在的值为" + getSingle(b));
}
}
//运算结果:
//唯一存在的值为3
//唯一存在的值为3
这种算法的处理方式,直接受到了你选择的排序算法的性能所影响。但是它很通用,不论你是多大倍数出现次数的数组,都能很好解决。
分析二
或许有些小伙伴不知道异或(^)操作的功能,异或操作具满足交换律和结合律,也就是说异或操作满足以下等式:
X^X=0
0^X=X
也就是说,如果只要同一个数字出现偶次,那么它就会抵消。我们可以遍历数组,把他们都进行一次异或操作,那么最后的结果就是那个存在一次的数。但是,它有一个很致命的缺陷,就是只试用于数组只出现偶次的情况。但是优点不可置否,只需要遍历一次数组即可。
实现代码二
package com.brickworkers;
/** * * @author Brickworker * Date:2017年6月28日下午2:25:55 * 关于类Example.java的描述:用异或运算寻找出现一次的数字,只试用于其他数字出现偶次的情况 * Copyright (c) 2017, brcikworker All Rights Reserved. */
public class Example {
public static int getSingle(int[] a){
//直接遍历数组
int result = a[0];
for (int i = 1; i < a.length; i++) {
result ^= a[i];
}
return result;
}
public static void main(String[] args) {
//除了唯一出现的其他出现2次
int[] a = {1,2,3,4,5,6,7,8,9,1,2,4,5,6,7,8,9};
System.out.println("唯一存在的值为" + getSingle(a));
}
}
//运算结果:
//唯一存在的值为3
//
代码简单而精炼,是一种非常优秀的解决方式。
分析三
第二种方法虽然高明,但是只能查找出现偶次成为了硬伤。如果出现倍数为奇次又该如何解决呢?用第一种方法又会显得算法时间复杂度很高。其实也有更好、更通用的解决办法:
试想存在一个值,这个值出现了多次,那么它的二进制每个位数上的1相加,肯定能被次数整除,比如说:
{3,3,3,2,1,1,1}
转化成二进制就变成了:
11,11,11,10,01,01,01
可以发现除却2的二进制10以外,剩余的6个数字,低位和高位相加分别是3和6,都可以被3整除。那么要寻找这个2,可以通过下面这个算式计算:
低位:
(1+1+1+1+1+1)/3 = 0
高位:
(1+1+1+1)/3 = 1 PS:其中一个1是2的二进制中来的
那么这个出现一次的数字就是 高位+低位 = 10。
实现代码三
package com.brickworkers;
/** * * @author Brickworker * Date:2017年6月28日下午2:25:55 * 关于类Example.java的描述:用位操作的规律实现通用的方法 * Copyright (c) 2017, brcikworker All Rights Reserved. */
public class Example {
public static int getSingle(int[] a, int times) {
// 定义一个数组,用于存放每一个位上出现次数
int[] count = new int[32]; // int类型占4个字节32位
// 遍历数组,计算每个位置上出现的次数
for (int i = 0; i < a.length; i++) {
// 计算每一个位上出现的次数
for (int j = 0; j < 32; j++) {
count[j] += ((a[i] >> j) & 1);//计算一个a[i]中的所以位上的出现情况。&操作:1&1=1;1&0=0
}
}
int result=0;//定义一个返回值
//遍历出现次数数组,如果存在某个位上的值不能被次数整除,那么唯一值就在这个位上
for(int i = 0; i < 32; i++){
if(count[i] % times !=0){
result += (1<<i);//恢复这个唯一的数,需要还原它原本1所在的位置
}
}
return result;
}
public static void main(String[] args) {
//除了唯一出现的其他出现3次
int[] a = {1,2,3,4,5,6,7,8,9,1,2,4,5,6,7,8,9,1,2,4,5,6,7,8,9};
System.out.println("唯一存在的值为" + getSingle(a, 3));
}
}
//运行结果:
//唯一存在的值为3
//
//
总结
在面试的过程中,一般的人都只能想到第一种方法。之所以想不到后面两种,很多程度上是不知道存在: ①X^X = 0;0^X=X,②各个位数值相加肯定能被次数整除 这样的规律。大神除外,反正笔者当时是没想到。
希望对你有所帮助