问题描述:
数组a[N], 1---N-1 这N-1个数存放在a[N]中,其中某个数重复了1次。写一个函数,找出被重复的数字。
要求:每个数组元素只能访问一次,并且不用辅助存储空间。
采用数学求和法,因为只有一个数字重复一次,而又是连续的,根据累加和原理,对数组的所有项求和,然后减去1---N-1的和,即为所求的重复数
代码:
/**
* 累加和原理
* 对数组所有项求和,然后减去1--N-1的值,就是所求的重复数
* @param a
* @return
*/
public int xor_findDup(int a[]){
int n = a.length;
int tmp1 = 0;
int tmp2 = 0;
for(int i=0; i<n-1; i++){
tmp1 += (i+1);
tmp2 += a[i];
}
tmp2 += a[n-1];
int result = tmp2 - tmp1;
return result;
}
如果没有要求每个数组元素只能访问1次,且不允许使用辅助存储空间,还可以异或法和位图法来求解
(1)异或法
数组a[N]中的N个数异或结果与 1---N-1异或的结果再做异或运算,得到的值即为所求。
设重复数为A,其余N-2个数异或结果为B, N个数异或结果为 A ^ A^B, 1--N-1异或结果为A^B, 则有:(A^B) ^ (A^A^B) = A^A
代码:
/**
* 异或法
* 数组中a[N]中的n个数异或结果 与 1--N-1异或的结果 再做异或运算,
* 得到的值即为所求
* A为重复数,B为N-2个数异或的结果
* @param a
* @return
*/
public int xor_findDup1(int a[]){
int n = a.length;
int i;
int result = 0;
// A^A^B
for(i = 0; i<n; i++){
result ^= a[i];
}
// 1---N-1 A^B
for(i=1; i<n; i++){
result ^= i;
}
return result;
}
(2)空间换时间法
申请长度为n-1的整形数组flag并初始化为0.然后从头开始遍历数组a,取每个元素a[i]的值,将其对应的数组flag中的元素赋值为1,r如果已经为1,那么该数就是重复的数
代码:
/**
* 空间换时间
* 如果数组flag中的元素已经为1,就说明是重复的数
* @param a
* @return
*/
public int xor_findDup2(int[] a){
int n = a.length;
boolean[] flag = new boolean[n];
int i=1;
int result = Integer.MAX_VALUE;
// 初始化
while(i<n){
flag[i] = false;
i++;
}//
for(i=0; i<n; i++){
// a[i]对应flag的索引
if(flag[a[i]] == false){
flag[a[i]] = true;
}else{
result = a[i];
break;
}
}//
return result;
}
(3)转化为“已知一个单链表中存在环,找出环的入口点”
将a[i] 看做第i个元素的索引,a[i] --> a[a[i]] --- > a[a[a[i]]] --->... ,最终形成一个单链表,由于数组中存在重复元素,因此一定存在一个环,且环的入口元素为重复元素。适合数组中有唯一一个重复元素的情况
关键在于,数组a的长度是n,而元素的范围是[1--n-1], 所以a[0] 不会指向自己,进而不会陷入错误的自循环。
代码:
public void findInteger(int[] a){
int x,y;
x= y =0;
do{
x = a[a[x]];
y = a[y];
}while(x!=y);
x=0;
do{
x = a[x];
y = a[y];
}while(x!=y);
System.out.println(x);
}
变形:至少存在一个重复数,即可能存在多个重复数,O(n)时间内找出其中任意一个重复数
1.位图法,时间复杂度为O(n),空间复杂度为O(n)
代码:
/**
* 位图法判断整形数组中的重复数字,时间复杂度为O(n)
* @param a
*/
public void WeiTuFindDuplicatedItem(int[] a){
// 找出数组中的最大值
int max=a[0];
int n = a.length;
for(int i=1; i<n; i++){
if(a[i] > max){
max = a[i];
}
}//
// 创建长度为max+1的数组
int bit[] = new int[max+1];
// 按值向新数组中添值
for(int val : a){
if(bit[val] != 0){
System.out.print(val+" ");
}else{
bit[val] = 1;
}
}//
}
2.数组排序法
时间复杂度为O(n),空间复杂度为O(n).先对数组进行计数排序,然后顺序扫描整个数组,一旦遇到一个已出现的元素,则直接将之输出。
3.取反法
如果遍历到数组中的元素为i, 那么把 a[i] 的值取反,如果i在数组中出现两次,那么a[i] 经过两次取反操作,值与原始值相等,且为正数; 如果i 出现了一次,那么a[i] 的值为原始值的相反数,且为负数。
实现方法:
将数组元素值作为索引,对于元素a[i],
如果 a[a[i]] >0, 那么设置 a[a[i]] = -a[a[i]],
如果 a[a[i]] <0, 那么设置a[-a[i]] = -a[-a[i]],
最后从数组第二个元素开始遍历数组,如果a[i] >0 , 那么这个数就是重复的。 由于在进行遍历后对数组中的数据进行了修改,因此需要对数据进行还原(对数组中的负数取反)
代码:
/**
* 取反法,只适合重复偶数次
* @param a
*/
public void qufanFindDuplicatedItem(int[] a){
int n = a.length;
int[] b = new int[n];
for(int i=0; i<n; i++){
if(b[a[i]] >= 0){
b[a[i]] = -a[i];
}else{
b[a[i]] = -b[a[i]];
}
}//
for(int i=1; i<n; i++){
if(b[i] > 0){
System.out.print(b[i]+" ");
}
}
}//