1. 程式人生 > >求陣列中第K大的數

求陣列中第K大的數

本題的的陣列是可以包含重複元素的,且要求時間複雜度控制在O(n)

解題思路:陣列中第k大的數等價於排序陣列中第n-k個數,直觀的想法是將陣列排序後取第n-k個數即可,但是最快的排序演算法時間複雜度也是O(nlogn), 可以參考快速排序一次劃分的思想,將時間複雜度降低為O(n);一次劃分可以講陣列分為三部分,比支點元素小或者與支點元素相等的部分,支點元素,比支點元素的部分。一次partition之後,可以得到這個支點元素的下標,並且這個下標,也就是說這個數字在陣列中的位置已經確定,它就是第n-index大的數,因此我們可以在一次劃分後,比較由這次劃分所確定的元素的下標index和n-k比較,如果相等,則arr[index]就是第n-k個數;若index> n - k,則證明第k大的數在 index的左邊,繼續在左邊進行一次劃分,;若index < n -k,則第k大的數在index的右邊,繼續在右邊進行一次劃分,直到使得index == n- k 為止。這裡運用了分治的思想。

public class Main {

	public static int findKthMax(int[] arr, int k) {

		if (arr == null || arr.length == 0 || k > arr.length) {
			return Integer.MIN_VALUE;
		}
		int length = arr.length;
		int start = 0;
		int end = length - 1;
		// 對陣列進行一次劃分
		int index = partition(arr, start, end);
		//直到index為第n-k個數為止
		while (index != length - k) {
			if (index > length - k) {
				//說明第length - k 個數在陣列的左邊,繼續在上次劃分的左邊裡尋找
				end = index - 1;
				index = partition(arr, start, end);
			} else {
				//說明第length - k 個數在陣列的右邊,繼續在上次劃分的右邊尋找
				start = index + 1;
				index = partition(arr, start, end);
			}
		}
		
		return arr[index];

	}

	/**
	 * @param arr
	 *            帶劃分的陣列
	 * @param start
	 *            陣列的起始位置
	 * @param end
	 *            陣列的結束位置 return 返回陣列中某個元素的索引,索引左邊的元素都比該元素小或者相等,索引右邊的元素都比索引元素大
	 */
	public static int partition(int[] arr, int start, int end) {

		if (arr == null || arr.length == 0 || start < 0 || start >= arr.length || end < 0 || end >= arr.length
				|| start > end) {
			return -1;
		}

		// 將陣列的起始元素作為支點元素
		int pivot = arr[start];
		// left從陣列最左邊開始,往右移,直到找到一個比支點元素大的,待放入到陣列的右半部分
		int left = start;

		// right從陣列的最右邊開始,往左移,直到找到一個比支點元素小的或者相等的,待放入到陣列的左半部分
		// 那麼當left和right交叉時,right肯定是指向比支點元素小或者和支點元素想等的元素,此時right可以作為分界線
		// right左邊都是比支點元素小的或者相等的,right右邊肯定是比支點元素大的
		int right = end;
		while (left < right) {

			// left一直右移,直到找到一個比支點元素大的,待交換到陣列的右邊
			while (arr[left] <= pivot) {
				left++;
			}

			// right一直左移,直到找到一個比支點元素小或者和支點元素相等的元素,待交換到陣列的左邊
			while (arr[right] > pivot) {
				right--;
			}

			if (left < right) {
				swqp(arr, left, right);
				left++;
				right--;
			}
		}

		// 當left和right錯開後,此時已經將陣列分成兩個部分,right指向之前的包括right指向的元素都和支點元素相等或比支點元素小
		// left指向之後的元素包括left指向元素都比支點元素大
		// 將支點元素和right指向元素進行交換,right作為陣列的分割點
		arr[start] = arr[right];
		arr[right] = pivot;
		return right;
	}

	private static void swqp(int[] arr, int left, int right) {
		int temp = arr[left];
		arr[left] = arr[right];
		arr[right] = temp;
	}
	
}