1. 程式人生 > >面試題3:陣列中重複的數字(Java 實現)

面試題3:陣列中重複的數字(Java 實現)

題目一:在一個長度為n的數組裡的所有數字都在0到n-1的範圍內。 陣列中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出陣列中任意一個重複的數字。 例如,如果輸入長度為7的陣列{2,3,1,0,2,5,3},那麼對應的輸出是重複的數字2或者3。

方法一:時間複雜度為O(n*nlogn),空間複雜度為O(1)
先把輸入的陣列排序,排序一個長度為n的陣列需要 O(nlogn)的時間,然後只需從頭到尾掃描一次陣列,需要 O(n)的時間,比較 a[i] 是否等於 a[i+1],若相等,則說明有重複的元素,輸出 a[i] ,若不相等,說明陣列中沒有重複的元素。

方法二:時間複雜度O(n),空間複雜度O(n),以空間換時間
利用一個大小為O(n)的雜湊表來解決問題,首先從頭到尾掃描一次陣列,時間複雜度為 O(n),每掃描一個元素都可以用 O(1)的時間來判斷雜湊表中有沒有這個元素,若雜湊表中沒有這個元素,則把它加入到雜湊表中,若雜湊表中存在這個元素,說明這個元素重複了,就找到了一個重複的元素。

方法三:時間複雜度O(n),空間複雜度O(1)
首先從頭到尾掃描陣列,時間複雜度為 O(n),依次判斷陣列中的元素與其下標是否對應相等。當不相等時:分為兩種情況,如果這個元素與以這個元素值為下標的元素相等(元素本應該在的位置),則說明這個元素重複了;若不相等,交換元素,把元素放到與其下標對應的位置。以陣列{2,3,1,0,2,5,3,}為例,第0個元素(下標為0)值為2,與下標不對應相等,則在比較下標為2所在的元素,這裡是1,不相等,即交換2和1,把2放回與下標對應的位置。

測試用例:

  1. 功能測試:長度為n的陣列中包含一個或多個重複的數字。
  2. 邊界測試:陣列中不包含重複的數字
  3. 負面測試:輸入空指標:長度為n的陣列包含0——n-1之外的數字
import java.util.HashMap;

public class test_three {
	
	//方法二:利用一個大小為O(n)的雜湊表
	public boolean duplicate(int numbers[],int length,int [] duplication) {
        boolean flag = false;
        if(numbers==null || length==0){
            return flag;
        }
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for(int num: numbers){
            if(map.containsKey(num)){
                flag = true;
                duplication[0] = num;
                break;
            }
            map.put(num, 0);
        }
        return flag;
    }
	
	/*
	 方法三:時間複雜度O(n),空間複雜度O(1) 最優解法:雖然程式碼中有一個二重迴圈,但每個數字最多隻要交換兩次就能找到屬於它自己的
	 位置,因此總的時間複雜度是O(n)
	 */

	public boolean duplication(int[] numbers, int length, int[] duplication){
		
		//如果輸入的陣列為空,直接返回false
		if(numbers == null || length<=0)return false;
		
		//從頭到尾掃描陣列
		for(int i=0; i<length; i++){
			//如果陣列不在1——n-1的範圍內,直接返回false
			if(numbers[i]<0 || numbers[i]>=length)return false;
			//當陣列中某個元素不等於其下標時,分為兩種情況
			while(numbers[i]!=i){
				//如果這個元素與以這個元素值為下標的元素相等(元素本應該在的位置),則說明這個元素重複了
				if(numbers[i] == numbers[numbers[i]]){
					//把這個重複的元素儲存在duplication[]陣列中
					duplication[0] = numbers[i];
					return true;
				}else{         	//若不相等,即交換,把元素放到與下標對應的位置
					int temp = numbers[i];
					numbers[i] = numbers[temp];
					numbers[temp] = temp;
				}
			}
		}
		return false;
	}
}

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

分析1: 時間複雜度O(n),空間複雜度O(n)
由於不能修改輸入的陣列,我們可以建立一個長度為n+1的輔助陣列,然後逐一把原陣列的每個數字複製到輔助陣列。如果原陣列中被複制的數字是m,則把它複製到輔助陣列中下標為m的位置。如果下標為m的位置上已經有數字了,則說明該數字重複了。由於使用了輔助空間,故該方案的空間複雜度是O(n) 。

public class Solution {

    public static int getDuplication(int[] arr) {        if (arr == null || arr.length == 0) {
            return -1;
        }
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < 1 || arr[i] >= arr.length) {
                return -1;
            }
        }

        int[] tempArr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == tempArr[arr[i]]) {
                return arr[i];
            }
            tempArr[arr[i]] = arr[i];
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] numbers = { 2, 1, 5, 4, 3, 2, 6, 7 };
        System.out.println(getDuplication(numbers));
    }
}

分析二: 時間複雜度O(nlogn),空間複雜度O(1)
由於分析一的空間複雜度是O(n),因此我們需要想辦法避免使用輔助空間。我們可以這樣想:如果陣列中有重複的數,那麼n+1個1~n範圍內的數中,一定有幾個數的個數大於1。那麼,我們可以利用這個思路解決該問題。

利用二分查詢演算法,我們把從1–n的數字從中間的數字m分為兩部分,前面一半為1–m,後面一半為m+1–n。如果1–m的數字的數目等於m,則不能直接判斷這一半區間是否包含重複的數字,反之,如果大於m,那麼這一半的區間一定包含重複的數字;如果小於m,另一半m+1–n的區間裡一定包含重複的數字。接下來,我們可以繼續把包含重複的數字的區間一分為二,直到找到一個重複的數字。

這個方法不能保證找到所有重複的元素。

public class Solution {

    public static int getDuplication(int[] arr) {       
    
     if (arr == null || arr.length == 0) {
            return -1;
        }
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < 1 || arr[i] >= arr.length) {
                return -1;
            }
        }

        int start = 1;
        int end = arr.length - 1;
        int mid = 0;
        int count = 0;
        while (start <= end) {
            if (start == end) {
                count = countRange(arr, start, end);
                if (count > 1) {
                    return start;
                } else {
                    break;
                }
            }
            mid = (start + end) / 2;
            count = countRange(arr, start, mid);
            if (count > mid - start + 1) {
                end = mid;
            } else {
                start = mid + 1;
            }
        }
        return -1;
    }

    public static int countRange(int[] arr, int start, int end) {
        int count = 0;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] >= start && arr[i] <= end) {
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        int[] numbers = { 1, 3, 5, 4, 2, 5, 6, 7 };
        int result = Solution.getDuplication(numbers);
        System.out.println(result);
    }
}