数据流中的第K大值 之堆的特点 + 原生堆

前言

堆是一个很有特点的排序方法,它可排序,也可只取前K个,配合K大的窗口。原生堆比优先队列快且灵活,原生堆的核心在于建堆和调整。

一、案例

在这里插入图片描述

二、K大窗口

1)优先队列来维持K大窗口
2)原生堆来维持K大窗口
3)List + 二分来确定第K个元素

package com.xhu.offer.offerII;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;

//数据流的第K大数值
public class KthLargest {
    //list+二分
    List<Integer> cache = new ArrayList<>();
    int k;

    public KthLargest(int k, int[] nums) {
        this.k = k;

        Arrays.sort(nums);

        for (int num : nums) cache.add(num);
    }

    public int add(int val) {
        int idx = binarySearch(this.cache, val);

        cache.add(idx, val);

        return cache.get(cache.size() - k);
    }

    //统一的二分
    private int binarySearch(List<Integer> cache, int target) {
        int low = 0, high = cache.size();

        while (low < high) {
            int mid = low + high >>> 1;
            int midVal = cache.get(mid);

            if (midVal < target) low = mid + 1;
            else high = mid;
        }
        return high;
    }
}

class KthLargest2 {
    //维持一个K大窗口即可,原生堆,不难,而且速度自然比优先队列快,加上是自定义,灵活。
    int[] nums;

    public KthLargest2(int k, int[] nums) {
        this.nums = new int[k];
        this.nums[k - 1] = Integer.MIN_VALUE;

        int i = 0;
        for (; i < k && i < nums.length; i++) this.nums[i] = nums[i];

        //建堆
        for (int j = k >>> 1; j >= 0; j--) adjustHeap(j);
        //将堆顶的小元素去掉,维持K大的堆。
        while (i++ < nums.length) {
            if (nums[i - 1] < this.nums[0]) continue;

            this.nums[0] = nums[i - 1];
            adjustHeap(0);
        }
    }

    private void adjustHeap(int k) {
        int val = nums[k];

        for (int i = 2 * k + 1; i < nums.length; i = i * 2 + 1) {
            if (i + 1 < nums.length && nums[i] > nums[i + 1]) i++;
            if (nums[i] > val) break;

            nums[k] = nums[i];
            k = i;
        }
        nums[k] = val;
    }

    public int add(int val) {
        if (val > nums[0]) {
            nums[0] = val;

            adjustHeap(0);
        }
        return nums[0];
    }
}

class KthLargest3 {
    //维持一个K大窗口即可,优先队列
    PriorityQueue<Integer> queue = new PriorityQueue<>();
    int k;

    public KthLargest3(int k, int[] nums) {
        this.k = k;

        Arrays.sort(nums);

        int len = nums.length;

        for (int i = len - 1; i >= 0 && i + k >= len; i--) queue.offer(nums[i]);
    }

    public int add(int val) {
        queue.offer(val);

        if (queue.size() > k) queue.poll();

        return queue.peek();
    }
}

总结

1)堆是一个很有特点的排序方法,它可排序,也可只取前K个,配合K大的窗口。
2)原生堆比优先队列快且灵活,原生堆的核心在于建堆和调整。

参考文献

[1] LeetCode 原题

附录

1、出现频率最高的K个数字

在这里插入图片描述

package com.xhu.offer.offerII;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;

//出现频率最高的K个数字
public class TopKFrequent {
    //map统计个数,堆找前K个
    public int[] topKFrequent(int[] nums, int k) {
        PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue());

        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) map.put(num, map.getOrDefault(num, 0) + 1);

        Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) queue.offer(iterator.next());

        int[] res = new int[k];
        int count = 0;
        while (count++ < k) res[count - 1] = queue.poll().getKey();

        return res;
    }

    //map统计个数,原生堆找前K个
    public int[] topKFrequent2(int[] nums, int k) {
        int[][] cache = new int[2][k];
        int size = 0;
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) map.put(num, map.getOrDefault(num, 0) + 1);

        Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext() && size < k) {
            Map.Entry<Integer, Integer> next = iterator.next();
            cache[0][size] = next.getKey();
            cache[1][size++] = next.getValue();
        }
        //建堆
        for (int i = k >>> 1; i >= 0; i--) adjustHeap(cache, i);
        //维持K大窗口
        while (iterator.hasNext()) {
            Map.Entry<Integer, Integer> next = iterator.next();
            if (next.getValue() < cache[1][0]) continue;
            cache[0][0] = next.getKey();
            cache[1][0] = next.getValue();
            adjustHeap(cache, 0);
        }
        return cache[0];
    }

    private void adjustHeap(int[][] nums, int k) {
        int val = nums[1][k];
        int key = nums[0][k];
        for (int i = 2 * k + 1; i < nums[0].length; i = i * 2 + 1) {
            if (i + 1 < nums[0].length && nums[1][i] > nums[1][i + 1]) i++;
            if (nums[1][i] > val) break;
            nums[0][k] = nums[0][i];
            nums[1][k] = nums[1][i];
            k = i;
        }
        nums[0][k] = key;
        nums[1][k] = val;
    }

}

2、和最小的K个数对

对于堆问题,也可以用K大的list窗口来二分来解决,毕竟二分也是O(logN)
在这里插入图片描述

package com.xhu.offer.offerII;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

//和最小的k个数对
public class KSmallestPairs {
    //
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        List<List<Integer>> res = new ArrayList<>();
        int i = 0;
        for (; res.size() < k && i < nums1.length; i++) {
            for (int j = 0; res.size() < k && j < nums2.length; j++) {
                List<Integer> el = new ArrayList<>();
                el.add(nums1[i]);
                el.add(nums2[j]);
                res.add(el);
            }
        }
        res.sort((o1, o2) -> o1.get(0) + o1.get(1) - o2.get(0) - o2.get(1));
        for (; i < nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                int m = res.get(res.size() - 1).get(0) + res.get(res.size() - 1).get(1);
                int n = nums1[i] + nums2[j];
                if (n >= m) break;
                int idx = binarySearch(res, n);
                res.remove(res.size() - 1);
                List<Integer> el = new ArrayList<>();
                el.add(nums1[i]);
                el.add(nums2[j]);
                res.add(idx, el);
            }
        }
        return res;
    }

    private int binarySearch(List<List<Integer>> var, int target) {
        int low = 0, high = var.size() - 1;

        while (low < high) {
            int mid = low + high >>> 1;
            int midVal = var.get(mid).get(0) + var.get(mid).get(1);

            if (midVal > target) high = mid - 1;
            else low = mid + 1;
        }
        return low;
    }

    public static void main(String[] args) {
        List<Integer> el = new ArrayList<>();
        el.add(1);
        el.add(0, 0);
        System.out.println(Arrays.toString(el.toArray()));
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值