以下几道关于数组的编程题,都是用同一个思路扩展出来的,和朋友讨论的时候感觉好有趣,便记录下来。以下是题目:
- 给你n个数,其中有且仅有一个数出现了奇数次,其余的数都出现了偶数次。用线性时间常数空间找出出现了奇数次的那一个数。
- 1到n之间的n - 1个不重复的数,如何快速找出缺少的那个数?
- 1到n之间的n - 2个不重复的数,又如何快速找出缺少的那两个呢?
- 给定一个数组,除了一个数出现1次之外,其余数都出现3次。找出出现一次的数。
给你n个数,其中有且仅有一个数出现了奇数次,其余的数都出现了偶数次。用线性时间常数空间找出出现了奇数次的那一个数。
由于我是学电子出身的,对数字电路里面的异或门印象还是比较深的,即一个两个输入端一个输出端的门电路,输入0和0时输出为0,输入0和1时输出为1,输入1和0时输出为1,输入1和1时输出为0。 异或门
解决这道题的思路就是用异或,如果一个数组里面一个数出现了偶数次,那么这个数自己异或自己偶数次结果肯定是0,那么一个数异或0的到的是这个数自己。于是从头到尾异或一遍,最后得到的那个数就是出现了奇数次的数。 代码很简单:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Array1 {
public static void main(String[] args) throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String input = reader.readLine();
String[] numbers = input.split(" ");
int[] num = new int[numbers.length];
for(int j=0;j<numbers.length;j++) {
num[j] = Integer.parseInt(numbers[j]);
}
int odd = findOdd(num);
System.out.println(odd);
}
private static int findOdd(int[] num) {
int odd = 0;
for (int i : num) {
odd = odd ^ i;
}
return odd;
}
}
1到n之间的n - 1个不重复的数,如何快速找出缺少的那个数?
那么题目变成这样,要如何去想呢?
同样我们还是用上面异或的思路,想想,1到n只少一个数,那么我们把这和数组里面的数先全部异或一遍,再异或1到n里面的n个数,不就变成上面那道题了么。即除了缺少的那个数只出现了一次,其余的都出现了两次,对吧,这样得出的结果就是那个缺少的数。
那么题目再换一下呢?
1到n之间的n - 2个不重复的数,又如何快速找出缺少的那两个呢?
还是用异或的方法~
从头到尾异或一遍,再异或1到n里面的n个数,你就得到了需要求的两个数异或后的值。这两个数显然不相等,异或出来的结果不为0。我们可以据此结果第其中一位为1的那一位,然后把所有这个数组里面n-2个数分成两类,在那一位上是0的分成一类,在那一位上是1的分到另一类。
同样把1到n这n个数也按照这个方式分成两类。再把含有那一位上是0的那两类的数异或,就得到了其中一个缺少的数。把含有那一位上是1的那两类的数异或,就得到了另外一个缺少的数。
import java.util.*;
public class Test1 {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
String[] numbers = input.split(" ");
int[] num = new int[numbers.length];
for(int j=0;j<numbers.length;j++) {
num[j] = Integer.parseInt(numbers[j]);
}
int n = 0;
int max = Integer.MIN_VALUE;
for (int i : num) {
n = n ^ i;
max = Math.max(max, i);
}
for (int i = 1; i <= max; i++) {
n = n ^ i;
}
n = n & ~(n-1);
int a = 0;
int b = 0;
for (int i : num) {
if((i&n) == 0){
a = a ^ i;
} else {
b = b ^ i;
}
}
for (int i = 1; i <= max; i++) {
if((i&n) == 0){
a = a ^ i;
} else {
b = b ^ i;
}
}
System.out.println(a);
System.out.println(b);
}
}
给定一个数组,除了一个数出现1次之外,其余数都出现3次。找出出现一次的数。
那题目再换成这样呢?哈哈,这才是精华。还记得第一道题的时候说这个思路是从数字电路里面的异或门得到启发的么?现在题目变成了出现3次,怎么破?
前面说到异或门是有两个输入的。那么现在题目变成了出现3次,我们就改成3个输入的门呗?
再来看看异或门的原理是什么样子的,其实是个二进制的加法,0+0=0;0+1=1;1+0=1;1+1=0(进位了,所以为0)。
OK,那么现在改成有三个输入的门,那我们就把他变成3进制的加法呗?即:0+0=0;0+1=1;1+0=1;1+1=2;0+2=2;2+0=0;1+2=0(进位了,所以为0);2+1=0(进位了,所以为0),2+2=1。
所以这道题的思路是和上面的一样的,只不过把异或中的二进制换成了三进制。这样理解了吗?
那么如何计算三进制呢?我们可以用两个辅助的数a和b来计算:
b = a & (b ^ num);
a = b | (a ^ num);
- 初始值:a=0,b=0
- 当num第一次出现的时候,b=0 , a=num:
- 当num第二次出现的时候,b=num , a=num;
- 当num第三次出现的时候,b=0 , a=0;
public static int search(int[] arr) {
int a = 0, b = 0;
for (int i = 0; i < arr.length; i++) {
b = a & (b ^ arr[i]);
a = b | (a ^ arr[i]);
}
return a;
}
所以这几道题都是由异或的原理引出来的~~~以上是若有什么不对的地方请指正~