经典数据结构题目-堆

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;
    }
posted @ 2024-02-06 23:44  gg12138  阅读(15)  评论(0编辑  收藏  举报