题目
设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。
你的 KthLargest 类需要一个同时接收整数 k 和整数数组nums 的构造器,它包含数据流中的初始元素。每次调用 KthLargest.add,返回当前数据流中第K大的元素。
示例:
int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5
kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8
kthLargest.add(4); // returns 8
说明:
你可以假设 nums 的长度≥ k-1 且k ≥ 1。
说明
由于想练习下堆的操作,于是在leetcode中直接索引 堆 关键字 找到该题,所以思路方面没多考虑就选择了堆来完成此题。
面试常见题 100亿个数里找最大的前K个数 亦采用此思路。
思路
思路清晰,构建一个容量为k的小根堆(即堆头节点最小),因为题目是求第k大的元素,构建小根堆后即为堆头节点即为所求。每次添加元素都调整堆(替换堆头节点)
具体步骤:
1 使用nums的前k个元素构建一个小根堆(createMinHeap),即数组a。
这步有一个需要考虑的情况nums.length==k-1(因为题目只限定nums.length>=k-1),我采取的方法是先复制nums数组到a数组,然后a的最后一个元素直接填充为Integer.MIN_VALUE;
2 将nums后边的元素添加到堆中(addNode);
3 每添加一个节点就进行一次addNode,并返回堆顶元素,即a[0];
代码
class KthLargest {
private int[] a = null;
public KthLargest(int k, int[] nums) {
a = new int[k];
if(nums.length>k-1){
System.arraycopy(nums,0,a,0,k);
}else{
System.arraycopy(nums,0,a,0,k-1);
a[k-1] = Integer.MIN_VALUE;
}
createMinHeap(k,nums);
}
public void createMinHeap(int k,int[] nums){
for(int i=k/2-1;i>=0;i--){
swapChild(i,k);
}
for(int i=k;i<nums.length;i++){
addNode(nums[i]);
}
}
public void swapChild(int i,int k){
int min = a[i];
int minIndex = i;
if(i*2+1<k&&min>a[i*2+1]){
min = a[i*2+1];
minIndex = i*2+1;
}
if(i*2+2<k&&min>a[i*2+2]){
min = a[i*2+2];
minIndex = i*2+2;
}
if(minIndex!=i){
swap(a,i,minIndex);
swapChild(minIndex,k);
}
}
public void addNode(int val){
if(a[0]<val){
a[0] = val;
swapChild(0,a.length);
}
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public int add(int val) {
addNode(val);
return a[0];
}
}
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest obj = new KthLargest(k, nums);
* int param_1 = obj.add(val);
*/
附:若题目的给定数据是固定的(如leetcode 215. 数组中的第K个最大元素)采用快速排序也是一种不错的思路。
堆排序的优势在于能够将占用的内存空间维持在O(k),很适合数据量很大,k很小的场景。另外对于类似此题的动态增加数据时并重新更新第k大元素很方便。