经典数据结构题目-堆
堆
215. 数组中的第K个最大元素
解法一
-
思路
-
使用堆排序,可以使用O(nlogN)找到前k个元素
- 先建立大顶堆,堆顶元素即为最大值。循环将最大值移除数组,并调整剩下元素的堆。循环k-1次后,第0个元素即是第k大的值
-
建立大顶堆
- 性质:根节点大于左右子树
- maxHeapify操作:自上向下,调整每个节点,保证其大于左右子树
- 自下向上,对每个节点都进行maxHeapify操作即可建立大顶堆
-
-
代码
// 使用堆排序
public int findKthLargest(int[] nums, int k) {
// 构建最大堆
buildMaxHeap(nums,nums.length);
int heapSize = nums.length;
// 不断将确定好的最大值移动到最后,移动k-1次后可以确定最大值
for(int i = nums.length - 1; i >= nums.length - k + 1; i--){
// 堆顶元素为当前最大值,转移到最后面去。剩下元素再重新进行堆调整
swap(nums,0,i);
heapSize --;
maxHeapify(nums,0,heapSize);
}
return nums[0];
}
// 调整为最大堆
public void buildMaxHeap(int[] a, int heapSize){
// 倒序调整每颗树
for(int i = heapSize / 2; i >= 0; i--){
maxHeapify(a,i,heapSize);
}
}
// 调整i节点所在树为最大堆
public void maxHeapify(int[] a, int i, int heapSize){
// 找到根左右子树中,最大的数的索引
int l = i * 2 + 1;
int r = i * 2 + 2;
int currMaxIndex = i;
if(l < heapSize && a[l] > a[currMaxIndex]){
currMaxIndex = l;
}
if(r < heapSize && a[r] > a[currMaxIndex]){
currMaxIndex = r;
}
// 最大值和根进行交换,保证根为树最大值
if(currMaxIndex != i){
swap(a,i,currMaxIndex);
// 重新调整 和根交换的节点
maxHeapify(a,currMaxIndex,heapSize);
}
}
解法二
- 思路
- 基于快排思想
- 在区间[left,right]中随机选择一个元素进行分区,大于该元素放在右,小于该元素放在左,就可以确定该元素在数组的第几位
- 找第k大,就是找第len-k位
- 同时根据随机选的和目标k位做比较,缩小搜索区间。随机index < len-k,说明target在index的右边,继续搜索[left+1,right]
- 基于快排思想
- 代码
public int findKthLargest(int[] nums, int k) {
int target = nums.length - k;
int left = 0;
int right = nums.length-1;
while(left <= right){
int index = partition(nums,left,right);
if(index == target){
return nums[index];
}else if(index < target){
left = index + 1;
}else{
right = index - 1;
}
}
return -1;
}
// 双路快排
private int partition(int[] nums, int left, int right){
// 取left为随机位置
int privot = nums[left];
// [left+1,le) <= privot
// (ge,right] => privot
int le = left + 1;
int ge = right;
while(le <= ge){
// 移动小于privot的指针
while(le <= ge && nums[le] < privot){
le ++;
}
// 移动大于privot的指针
while(le <= ge && nums[ge] > privot){
ge--;
}
if(le >= ge){
break;
}
swap(nums,le,ge);
le ++;
ge --;
}
// ge所在位置为privot的位置
swap(nums,left,ge);
return ge;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
295. 数据流的中位数
- 思路
- 使用两个小顶堆分别来保存大于中位数,和大顶堆保存小于等于中位数堆
- 添加
- 两个堆数量相等。往大于的堆添加,同时拉取一个元素到小于的堆。小于的堆数量比大于多1,即为中位数
- 两个堆数量不相等。基于上一条,小于的堆数量较大,加入小于的堆,同时拉取堆顶的元素到大于的堆,确保小于的堆只比大于多出一个元素
- 代码
Queue<Integer> gt;
Queue<Integer> lt;
// 两个堆分别保存大于堆的一半和小于堆的一半
// 添加时如果两个堆元素数量相等,往大于的堆添加元素,同时拉取一个元素到小于的堆,确保两个堆不相等时,小于的堆元素多于大于的堆。取中位数就取小于的
public MedianFinder() {
gt = new PriorityQueue<>(); // 小顶堆,保存较大的一半
lt = new PriorityQueue<>((x,y)->(y-x)); // 大顶堆,保存较小的一半
}
public void addNum(int num) {
if(gt.size() == lt.size()){
gt.add(num);
lt.add(gt.poll());
}else{
lt.add(num);
gt.add(lt.poll());
}
}
public double findMedian() {
return gt.size() != lt.size() ? lt.peek() : (gt.peek() + lt.peek()) / 2.0;
}
企鹅号:1272420336,一起学习,一起进步~