[Leetcode] 215. Kth Largest Element in an Array

本文介绍几种有效的方法来找出无序数组中的第K大元素,包括使用最小堆和基于快速排序的partition算法。

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Example 1:

Input: [3,2,1,5,6,4] and k = 2
Output: 5

Example 2:

Input: [3,2,3,1,2,4,5,5,6] and k = 4
Output: 4

Note: 

You may assume k is always valid, 1 ≤ k ≤ array's length.


这一题最直观的做法就是做个排序,然后返回第k个元素。复杂度O(nlogn)。这个做法太傻,就不写了。。

稍微看上去好一点的是用PriorityQueue(也就是最小堆),逐个遍历数组,然后维持一个大小为k的最小堆priorityqueue,最后堆顶就是要求的答案。 给出代码如下:

    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> pQ = new PriorityQueue<>(k);
        for (int i : nums) {
            if (pQ.size() < k) {
                pQ.add(i);
            } else if (pQ.peek() < i) {
                pQ.poll();
                pQ.add(i);
            }
        }
        
        return pQ.peek();
    }
这个算法的复杂度是时间O(nlogk), 空间复杂度是o(k)。基于k <= n 这个限制,我们可以认为这个算法是优于最原始的那个的。

再好一点或者说看起来高端一点的,就是一种叫selection ranki的算法。这个算法的基础是quicksort中的partition。

partition就是在数组中或者数组的给定范围中选取一个pivot(任意选取,一般是选取数组的给定范围中的最后一个或者用随机数去选取保证正态分布下是不存在最坏的情况的),然后将数组根据pivot分成左右两边,pivot左边的比pivot小,右边比pivot大。partition的代码如下:

    public int partition(int[] nums, int start, int end) {
       Random rand = new Random();
       int pivot = rand.nextInt(end - start + 1) + start;
        int k = nums[pivot];
        swap(nums, pivot, end);
        int j = start;
        for (int i = start; i < end; i++) {
            if (k < nums[i]) {
            	swap(nums, i, j);
                j++;
            }
        }
        
        swap(nums, end, j);
        return j;        
    }
在quicksort里,当你分开两边之后,你就对两边进行递归式的partition直到start和end相同为止。
但是在Selection Rank里面,你所要做的就是对其中一边进行partition就可以了。譬如说在一个size为10的数组里,你要选第5大的,那么你第一次做partition时,你返回的j如果是6(j在这里表示pivot在数组范围内是第几大)。那么你要求的数字肯定还在pivot的左边,因为pivot左边的数字都比pivot小,那么就对pivot左边的数字再做一次parition。如果返回的是3,那就对pivot右边的数组范围再做parition。按照这样的逻辑不停缩小范围,直到上面partition代码返回的j就是你要的k为止。然后nums[k]就会是第k大的数字。给出代码如下:
    public int partition(int[] nums, int start, int end) {
       Random rand = new Random();
       int pivot = rand.nextInt(end - start + 1) + start;
        // int pivot = end;
        int k = nums[pivot];
        swap(nums, pivot, end);
        int j = start;
        for (int i = start; i < end; i++) {
            if (k < nums[i]) {
            	swap(nums, i, j);
                j++;
            }
        }
        
        swap(nums, end, j);
        return j;        
    }
    
    public void swap(int[] arr, int a, int b) {
    	if (a != b) {
    		arr[a] ^= arr[b];
			arr[b] ^= arr[a];
    		arr[a] ^= arr[b];
    	}
    }
    
    public void kPartition(int[] nums, int k) {
        int start = 0, end = nums.length - 1;
        while (end != k - 1) {
            int pivot = partition(nums, start, end);
            if (pivot == k - 1) {
                return;
            } else if (pivot < k - 1) {
                start = pivot + 1;
            } else {
                end = pivot - 1;
            }
        }
    }

    public int findKthLargest(int[] nums, int k) {
        kPartition(nums, k);
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < k; i++) {
            min = Math.min(nums[i], min);
        }
        
        return min;
    }

这种做法的复杂度分为两种说法,最坏的是O(n^2),平均是O(n)。

最坏的情况就是你选出来的pivot恰好都是当前数组范围的边界值(最大或者最小)。那么你的效率就会是n + (n - 1) + (n - 2) + ... + 1,也就是O(n^2)的级别。

但如果你的pivot服从正态分布(这也是为什么用random来选取pivot的原因,这个是一种近似正态分布的做法)。你的效率就会是 n + (n / 2) + (n / 4) + (n / 8) + ... + 1,也就是O(n)的级别。

下载前必看:https://pan.quark.cn/s/a4b39357ea24 在本资料中,将阐述如何运用JavaScript达成单击下拉列表框选定选项后即时转向对应页面的功能。 此种技术适用于网页布局中用户需迅速选取并转向不同页面的情形,诸如网站导航栏或内容目录等场景。 达成此功能,能够显著改善用户交互体验,精简用户的操作流程。 我们须熟悉HTML里的`<select>`组件,该组件用于构建一个选择列表。 用户可从中选定一项,并可引发一个事件来响应用户的这一选择动作。 在本次实例中,我们借助`onchange`事件监听器来实现当用户在下拉列表框中选定某个选项时,页面能自动转向该选项关联的链接地址。 JavaScript里的`window.location`属性旨在获取或设定浏览器当前载入页面的网址,通过变更该属性的值,能够实现页面的转向。 在本次实例的实现方案里,运用了`eval()`函数来动态执行字符串表达式,这在现代的JavaScript开发实践中通常不被推荐使用,因为它可能诱发安全问题及难以排错的错误。 然而,为了本例的简化展示,我们暂时搁置这一问题,因为在更复杂的实际应用中,可选用其他方法,例如ES6中的模板字符串或其他函数来安全地构建和执行字符串。 具体到本例的代码实现,`MM_jumpMenu`函数负责处理转向逻辑。 它接收三个参数:`targ`、`selObj`和`restore`。 其中`targ`代表要转向的页面,`selObj`是触发事件的下拉列表框对象,`restore`是标志位,用以指示是否需在转向后将下拉列表框的选项恢复至默认的提示项。 函数的实现通过获取`selObj`中当前选定的`selectedIndex`对应的`value`属性值,并将其赋予`...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值