1. 程式人生 > >劍指Offer(第二版)面試題3:陣列中的重複元素

劍指Offer(第二版)面試題3:陣列中的重複元素

從今天開始,學習劍指Offer(第二版)中的所有演算法題,並且用java實現一遍,同步更新Blog

劍指Offer(第二版)面試題3:陣列中重複的數字

題目一:找出陣列中重複的數字 (限定不可以重複數字是-1,如果沒有重複數字,返回-1)
       在一個長度為n的數組裡得所有數字都在0~n-1的範圍內。陣列中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請判斷。
例如,輸入長度為7的陣列{2,3,1,0,2,5,3}那麼對應的輸出是重複的數字2或者3

題目一的三種解法如下:

/**
	 * 方法1:先對陣列排序,時間複雜度是O(NlogN)
	 * 遍歷陣列,時間複雜度是O(N)
	 * 總的時間複雜度是O(NlogN)
	 * @param arr
	 * @return
	 */
	private int duplicationInArray(int[] arr,int n){
		// 判斷輸入資料的合法性
		if(arr==null||n<=0)
			return -1;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]<0||arr[i]>n-1)
				return -1;
		}
		Arrays.sort(arr);
		for (int i = 0; i < arr.length-1; i++) {
			if(arr[i]==arr[i+1])
				return arr[i];
		}
		return -1;
	}
	/**
	 * 方法2:利用Hash表來搞定,從頭到尾按順序掃描陣列的每個數字,每掃描到一個數字的時候,
	 * 用O(1)的時間來判斷雜湊表中是否有該數字;
	 * 如果沒有,則將其加入雜湊表,反之,找到了重複數字
	 * 時間複雜度為O(N),但是其提高時間效率是以一個大小為O(N)的雜湊表為代價。
	 * @param arr
	 * @return
	 */
	private int duplicationInArray2(int[] arr,int n){
		// 判斷輸入資料的合法性
		if(arr==null||n<=0)
			return -1;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]<0||arr[i]>n-1)
				return -1;
		}
		HashMap<Integer, Integer> map = new HashMap<>();
		for (int i = 0; i < arr.length; i++) {
			if(map.containsKey(arr[i])){
				return arr[i];
			}else {
				map.put(arr[i], 1);
			}
		}
		return -1;
	}
	/**
	 * 方法3:下邊是本題的最優解法:(思路很重要)
	 * 思路:注意到陣列中的數字都在0~n-1的範圍內。如果這個陣列中沒有重複的數字,那麼當陣列排序之後數字i將
	 * 出現在下標為i的位置,由於陣列中有重複的數字,有些位置可能存在多個數字,同時有些位置可能沒有數字
	 * 
	 * 步驟:重排陣列,從頭到尾依次掃描這個陣列中的每個數字,當掃描到下標為i的數字時,首先比較這個數字(用m表示)
	 * 是不是等於i。如果是,則接著掃描下一個數字;如果不是,則再拿它和第m個元素進行比較。如果他和第m個元素相等,
	 * 則找到了重複數字;如果他和第m個數字不相等,就把第i個數字和第m個數字進行交換,把m放在屬於它的位置。
	 * 時間複雜度為O(N),空間複雜度為O(1)
	 * @param arr
	 * @param n
	 * @return
	 */
	private int duplicationInArray3(int[] arr,int n){
		// 判斷輸入資料的合法性
		if(arr==null||n<=0)
			return -1;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]<0||arr[i]>n-1)
				return -1;
		}
		for (int i = 0; i < arr.length; i++) {
			while(arr[i]!=i){
				if(arr[i]==arr[arr[i]]){
					return arr[i];
				}else {
					// 交換位置
					int temp = arr[i];
					arr[i] = arr[arr[i]];
					arr[temp] = temp;
				}
			}
		}
		return -1;
	}


主要學習第三種方法的思路,時間複雜度為O(N),確實是一種優秀的演算法。

思路:注意到陣列中的數字都在0~n-1的範圍內。如果這個陣列中沒有重複的數字,那麼當陣列排序之後數字i將出現在下標為i的位置,由於陣列中有重複的數字,有些位置可能存在多個數字,同時有些位置可能沒有數字
  
步驟:重排陣列,從頭到尾依次掃描這個陣列中的每個數字,當掃描到下標為i的數字時,首先比較這個數字(用m表示)是不是等於i。如果是,則接著掃描下一個數字;如果不是,則再拿它和第m個元素進行比較。如果他和第m個元素相等,則找到了重複數字;如果他和第m個數字不相等,就把第i個數字和第m個數字進行交換,把m放在屬於它的位置。時間複雜度為O(N),空間複雜度為O(1)

題目二:不修改陣列,找出陣列中重複的數字(限定不可以重複數字是-1,如果沒有重複數字,返回-1)

在一個長度為n+1的數組裡得所有數字都在1~n的範圍內。所以陣列中至少有一個數字是重複的,請找出陣列中任意一個重複的數字,但不能修改輸入的陣列。例如,輸入長度為8的陣列{2,3,5,4,3,2,6,7}那麼對應的輸出是重複的數字2或者3。


題目二的三種解法:

/**
	 * 方法1:不修改陣列,使用map來搞定,時間複雜度為O(N),空間複雜度也為O(N)
	 * @param arr
	 * @param n
	 * @return
	 */
	private static int duplicationInArray4(int[] arr,int n){
		// 判斷輸入資料的合法性
		if(arr==null||n<=0)
			return -1;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]<1||arr[i]>n)
				return -1;
		}
		HashMap<Integer, Integer> map = new HashMap<>();
		for (int i = 0; i < arr.length; i++) {
			if(map.containsKey(arr[i])){
				return arr[i];
			}else {
				map.put(arr[i], 1);
			}
		}
		return -1;
	}
	/**
	 * 方法2:不修改陣列,新建一個長度為n+1的陣列,每次都將原陣列中的arr[i]複製到新陣列中下標為arr[i]的位置
	 * 因為沒有數字0,很容易發現重複數字
	 * 時間複雜度為O(N),空間複雜度也為O(N)
	 * @param arr
	 * @param n
	 * @return
	 */
	private static int duplicationInArray5(int[] arr,int n){
		// 判斷輸入資料的合法性
		if(arr==null||n<=0)
			return -1;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]<1||arr[i]>n)
				return -1;
		}
		int[] cp = new int[n];
		for (int i = 0; i < arr.length; i++) {
			if(cp[arr[i]]==0)
				cp[arr[i]] = arr[i];
			else
				return arr[i];
		}
		return -1;
	}
	/**
	 * 方法3:為了減小空間複雜度,可以犧牲掉一些時間複雜度
	 * 思路:二分查詢的思路;將1~n的數字從中間的數字m分為了兩部分,前面一半為1~m,後邊一半為m+1~n。
	 * 		遍歷陣列,如果1~m的數字的數目超過了m,則這一半的區間肯定包含重複數字;否則另一半肯定包含重複數字。
	 * 時間複雜度為O(NlogN),空間複雜度也為O(1)
	 * @param arr
	 * @param n
	 * @return
	 */
	private static int duplicationInArray6(int[] arr,int n){
		// 判斷輸入資料的合法性
		if(arr==null||arr.length==0)
			return -1;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]<1||arr[i]>n)
				return -1;
		}
		int start = 1;
		int end = n;
		while(start<=end){ 
			int mid = (end+start)/2;
			int count = countRange(arr,n,start,mid);
			if(end==start){
				if(count>1)
					return start;
				else
					break;	
			}
			if(count>(mid-start+1))
				end = mid;
			else
				start = mid+1;
		}
		return -1;
	}
	// 統計區間內的數字出現的次數
	private static int countRange(int[] arr, int n, int start, int end) {
		if(arr==null||n<=0)
			return 0;
		int count = 0;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]>=start&&arr[i]<=end)
				count++;
		}
		return count;
	}

上述基於二分查詢的演算法不能保證找出所有重複的數字,例如該演算法不能找出陣列{2,3,5,4,3,2,6,7}中重複的數字2.這是因為在1~2的範圍裡有1和2兩個數字,這個範圍的數字也出現了2次,此時該演算法不能確定是每個數字各出現一次還是某個數字出現了兩次。

如果對你有幫助,記得點贊哦~歡迎大家關注我的部落格,可以進群366533258一起交流學習哦~