数组:如何找出数组中唯一的重复元素

问题描述:

数组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]+" ");
			}
		}
	}//


 

猜你喜欢

转载自blog.csdn.net/weixin_38108266/article/details/81273981