面试题:数组中数字出现的次数
题目一:数组中只出现一次的两个数字
一个整型数组中,除了两个数字之外,其他数字都出现了两次。时间复杂度O(n),空间复杂度O(1)
题目二:数组中唯一只出现一次的数字
在一个数组中,除一个数字只出现一次之外,其他数字都出现了三次
思路:
题目一:
参考的其他po:
https://www.cnblogs.com/shiganquan/p/9350685.html
https://blog.csdn.net/koala_tree/article/details/79617607
由于要求时间复杂度O(n),所以不能用排序的方法(最低nlogn);空间复杂度为1,所以不能用哈希表(不能开辟新空间)
先将题目一分为两个部分:1)将两个只出现1次的数字分到两个子数组中;2)在只含1个只出现1次的数字,其他数字都出现两次的数组中,找出那个只出现1次的数字
先解决2):
使用异或:任何一个数字异或它自己都等于0
因此,如果一个数组中只有1个只出现1次的数字,其他数字都出现了2次,那么依次异或(data[1]异或data[2],再异或data[3],再异或……),出现过两次的数字都被异或掉了,只会剩下那个只出现过1次的数字
异或是在2进制上的,将10进制的数表达为2进制,如三个数1,2,2,3,3,则1异或2后得11,11异或2得01,01异或3为10,10异或3为01,换为10进制为1
再解决1):
在原始数组中,有两个不同的只出现过1次的数字,在依次异或之后,剩下的结果为num1和num2异或的结果,其结果表示的是num1和num2在2进制表达中,不相同的位置,我们取它们第一个不同的位置index对num1和num2进行区分,并以此来对原始数组进行子数组的划分
为什么可以以2进制在index位置是否为1或是否为0为划分:
对num1和num2来说,在index位置是不同的,因此肯定是一个为1,一个为0,可划分开
对出现了两次的其他数来说,相同的数在任意位置的值都是相同的,所以以index位置的值来划分,可将相同的数都划分到同一个子数组中,不会出现相同的两个数分别被分到两个子数组中的情况
方法:
基于以上思路,代码步骤为:
1)先对原始数组依次异或,得到异或结果
2)找到第一个1的位置(从右到左),将其定为index,用于子数组和num1、num2的划分
3)对原始数组依次判断index位置是否为1/0,并分别异或自己子数组的数
4)得到两个子数组的异或结果,就分别是num1和num2
题目二:
如果一个数字出现三次,那么它的二进制表示的每一位也出现三次,如果把所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位的和都能被3整除。
∴ 如果把数组中所有数字的二进制表示的每一位都加起来,如果某一位的和能被3整除,那么那个只出现一次的数字的二进制表示中对应的那一位是0,否则就是1
使用长度为32位的辅助数组存储二进制表达的每一位的和,是因为java的int数据类型是32位
【代码】
题目一:
public class Q56_1 {
public static void main(String[] args) {
int[] list = new int[] {2,4,3,6,3,2,5,5};
int[] nums = getOnce(list);
if(nums!=null) {
System.out.printf("%d %d",nums[0],nums[1]);
}
}
public static int[] getOnce(int[] list) {
if(list.length<2) {
System.out.println("less than two");
return null;
}
// 1) 先对原始数组依次异或,得到异或结果
int result = 0;
for(int i=0;i<list.length;i++) {
result = result^list[i];
}
// 2)找到第一个1的位置(从右到左),将其定为index,用于子数组和num1、num2的划分
int index = getIndex(result);
if(index==-1) {
return null;
}
// 3)对原始数组依次判断index是否为1/0,并分别异或自己子数组的数
int num1=0,num2=0;
for(int i=0;i<list.length;i++) {
if(is1(list[i],index)==1) {
num1 = num1^list[i];
}else {
num2 = num2^list[i];
}
}
// 4)得到两个子数组的异或结果,就分别是num1和num2
return new int[] {num1,num2};
}
// 2)找到第一个1的位置(从右到左),将其定为index,用于子数组和num1、num2的划分
public static int getIndex(int result) {
if(result==0) {
System.out.println("there are all double numbers!");
return -1;
}
int index = 0;
while((result&1)==0) {
result = result>>1;
index++;
}
return index;
}
// 判断数的index位置是否为1/0
public static int is1(int x,int index) {
x = x>>index;
return (x&1);
}
}
题目二:
public class Q56_2 {
public static void main(String[] args) {
int[] list = new int[] {2,6,5,4,2,6,4,2,4,6};
int x = getOnce(list);
System.out.println(x);
}
public static int getOnce(int[] list) {
// 数组长度为1
if(list.length==1) {
return list[0];
}
// 不满足条件的数组长度
if(list.length<4) {
System.out.println("wrong input!");
return -1;
}
// 按位加每一个数的每一位
int[] bitSum = new int[32];
for(int i=0;i<list.length;i++) {
int bitloc = 1;
for(int j=31;j>=0;j--) {
int bit = list[i]&bitloc;
if(bit!=0) {
bitSum[j]++;
}
bitloc = bitloc<<1;
}
}
// 对每一位整除3得结果——所求数的每一位是1还是0
int result = 0;
for(int i=0;i<32;i++) {
result = result<<1;
result+=bitSum[i]%3;
}
return result;
}
}