求数组中第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;
	}
	
}


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值