215. Kth Largest Element in an Array

题目:

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.

For example,
Given [3,2,1,5,6,4] and k = 2, return 5.

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

链接: http://leetcode.com/problems/kth-largest-element-in-an-array/
题解:

找数组中第k大的元素,可以用heap,Radix sort或者Quick-Select。不过Quick-Select的Time Compleixty只有在Amorized analysis上才是O(n)。下面是使用Java自带的priority queue min-heap来完成的,算是投机取巧了。二刷要补上Radix sort以及Quick-Select

Time Complexity - O(nlogn), Space Complexity - O(k)

public class Solution {
    public int findKthLargest(int[] nums, int k) {   // use min oriented heap,  store min value at top of the heap
        if(nums == null || nums.length == 0)
            return 0;
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>(k);
        
        for(int i = 0; i < nums.length; i++) {
            if(pq.size() < k)
                pq.offer(nums[i]);
            else {
                if(nums[i] > pq.peek()) {
                    pq.poll();              // remove min
                    pq.offer(nums[i]);
                }
            }
        }
        
        return pq.poll();
    }
}

 

二刷:

方法跟1刷一样,建立一个size 为k的min-oriented heap。因为题目要求第k大的元素,所以我们堆里面,遍历完毕后堆顶的元素就是第k大的元素,而其余元素都比这个元素大。三刷要研究quick-select和radit sort

Java:

Time Complexity - O(nlogn), Space Complexity - O(k)

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null | nums.length == 0) {
            return 0;
        }
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>(k);
        for (int i = 0; i < nums.length; i ++) {
            if (pq.size() < k) {
                pq.offer(nums[i]);
            } else if (nums[i] > pq.peek()) {
                pq.poll();
                pq.offer(nums[i]);
            }
        }
        return pq.peek();
    }
}

 

三刷:

比较绕的一点是,求kth largest elements,我们使用最小堆,每次堆的size() > k的话就poll(),最后堆顶元素就是第k大的,堆内其他元素都比堆顶元素大。

Java:

Using min-heap:

Time Complexity - O(nlogk), Space Complexity - O(k) 

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length < k) return 0;
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for (int i : nums) {
            pq.offer(i);
            if (pq.size() > k) pq.poll();
        }
        return pq.peek();
    }
}

 

Update:

突然明白为什么在面试的时候会有人纠结于pq的size是k还是k + 1了。假如初始化一个size为k的pq,那么在k = 1这种情况下,用下面的代码就有可能算不对结果。面试的时候记得顺着面试官的意思说,不就是聊天嘛,没必要太较真。

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> minPQ = new PriorityQueue<>(k + 1);
        for (int num : nums) {
            minPQ.offer(num);
            if (minPQ.size() > k) minPQ.poll();
        }
        return minPQ.peek();
    }
}

 

 

Using quick-select:

速度反而比使用min-heap慢。  这里我们使用quick-select,pick数组的第一个元素作为pivot,然后把大于nums[0]的元素都放到数组前部,小于nums[0]的元素放在数组后部。这样遍历完毕以后nums[k]就是第k大的。

注意这里在开头进行了k--, 把k从1 based转换为0 based,方便写代码和处理边界条件。

Time Complexity - Amortized O(n), worst case O(n2)  Space Complexity - O(1) 

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length < k) return 0;
        k--;
        int lo = 0, hi = nums.length - 1;
        while (lo < hi) {
            int j = partition(nums, lo, hi);
            if (j < k) lo = j + 1;
            else if (j > k) hi = j - 1;
            else return nums[k];
        }
        return nums[lo];
    }
    
    private int partition(int[] nums, int lo, int hi) {
        int i = lo, j = hi + 1;
        while (i < j) {
            while (nums[++i] > nums[lo]) if (i == hi) break;
            while (nums[--j] < nums[lo]) if (j == lo) break;
            if (i >= j) break;
            swap(nums, i, j);
        }
        swap(nums, lo, j);
        return j;
    }
    
    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

 

改进了一下quick-select, 在查找前按照塞神的建议先进行了O(n)的shuffle,速度果然快了不少, 从47ms到达了7ms 

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length < k) return 0;
        shuffle(nums);
        k--;
        int lo = 0, hi = nums.length - 1;
        while (lo < hi) {
            int j = partition(nums, lo, hi);
            if (j < k) lo = j + 1;
            else if (j > k) hi = j - 1;
            else return nums[k];
        }
        return nums[lo];
    }
    
    private void shuffle(int[] nums) {
        java.util.Random rand = new java.util.Random(System.currentTimeMillis());
        for (int i = 0; i < nums.length; i++) {
            int r = rand.nextInt(i + 1);
            swap(nums, i, r);
        }
    }
    
    private int partition(int[] nums, int lo, int hi) {
        int i = lo, j = hi + 1;
        while (i < j) {
            while (nums[++i] > nums[lo]) if (i == hi) break;
            while (nums[--j] < nums[lo]) if (j == lo) break;
            if (i >= j) break;
            swap(nums, i, j);
        }
        swap(nums, lo, j);
        return j;
    }
    
    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

 

 

Reference:

https://leetcode.com/discuss/38336/solutions-partition-priority_queue-multiset-respectively

https://leetcode.com/discuss/45627/ac-clean-quickselect-java-solution-avg-o-n-time

https://leetcode.com/discuss/36913/solutions-java-having-worst-time-complexity-with-explanation

https://leetcode.com/discuss/36991/java-quick-select

https://leetcode.com/discuss/36966/solution-explained

https://leetcode.com/discuss/88064/97%25-2ms-java-quick-select-solution

posted @ 2015-11-22 12:47  YRB  阅读(1392)  评论(0编辑  收藏  举报