堆的基础知识
最大堆(最小堆):每个节点的值总是大于(小于)或等于其任意子节点的值 最大堆(最小堆)可以用数组进行保存,如果一个节点在数组中的下标是i,则其父节点的下标为(i-2)/2,它的左右子节点为2i+1,2i+2。
最大堆添加元素
新元素添加到数组的末尾,然后上浮
最大堆删除顶部元素
删除顶部元素后,将数组末尾元素交换到顶部,然后下沉
堆的应用
堆的最大特点:最大值或者最小值位于顶部,只需要时间就可以求出数据集合中的最大值或最小值
堆经常用来求取数据集合中最大或最小的k个元素
Q59:数据流的第k大数字
题目(简单):设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。请实现 KthLargest 类:
- KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
- int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。 示例:
输入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]
解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3); // return 4
kthLargest.add(5); // return 5
kthLargest.add(10); // return 5
kthLargest.add(9); // return 8
kthLargest.add(4); // return 8
解题思路
利用最小堆解决,当最小堆元素小于k时,直接添加,若元素已经达到k,后来的元素和堆顶元素比较,如果比堆顶元素大,则弹出顶部元素,添加这一元素
class KthLargest {
private PriorityQueue<Integer> minHeap;
private int size;
public KthLargest(int k, int[] nums) {
size = k;
minHeap = new PriorityQueue<>();
for(int num:nums){
add(num);
}
}
public int add(int val) {
if(minHeap.size()<size){
minHeap.offer(val);
}else if(val > minHeap.peek()){
minHeap.poll();
minHeap.offer(val);
}
return minHeap.peek();
}
}
Q60:出现频率最高的k个数字
题目(中等):给定一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
public int[] topKFrequent(int[] nums, int k) {
//键放数,值放次数
Map<Integer,Integer> numToCount = new HashMap<>();
for(int num:nums){
numToCount.put(num,numToCount.getOrDefault(num,0) + 1);
}
Queue<Map.Entry<Integer,Integer>> minHeap = new PriorityQueue<>((e1,e2) -> e1.getValue() - e2.getValue());
//最小堆中如果没有就直接加入,如果有就与堆顶元素比较,谁出现次数多留谁
for(Map.Entry<Integer,Integer> entry :numToCount.entrySet()){
if(minHeap.size() < k){
minHeap.offer(entry);
}else{
if(minHeap.peek().getValue() < entry.getValue()){
minHeap.poll();
minHeap.offer(entry);
}
}
}
//存成一个数组
int[] result = new int[k];
int i = 0;
for(Map.Entry<Integer,Integer> entry:minHeap){
result[i] = entry.getKey();
i++;
}
return result;
}
Q61:和最小的k个数对
题目(中等):给定两个以升序排列的整数数组 nums1 和 nums2 , 以及一个整数 k 。定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。请找到和最小的 k 个数对 (u1,v1), (u2,v2) ... (uk,vk) 。
示例 1:
输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
输出: [1,2],[1,4],[1,6]
解释: 返回序列中的前 3 对数:[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
示例 2:
输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
输出: [1,1],[1,1]
解释: 返回序列中的前 2 对数:[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]
示例 3:
输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]
使用最大堆保存和最小的k个数对
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
Queue<int []> maxHeap = new PriorityQueue<>((p1,p2)->p2[0] + p2[1] - p1[0] - p1[1]);
for(int i = 0; i < Math.min(k,nums1.length);i++){
for(int j = 0;j < Math.min(k,nums2.length);j++){
if(maxHeap.size() < k){
maxHeap.offer(new int []{nums1[i],nums2[j]});
}else{
int [] root = maxHeap.peek();
if(root[0] + root[1] > nums1[i] + nums2[j]){
maxHeap.poll();
maxHeap.offer(new int []{nums1[i],nums2[j]});
}
}
}
}
List<List<Integer>> result = new LinkedList<>();
while(!maxHeap.isEmpty()){
int [] vals = maxHeap.poll();
result.add(Arrays.asList(vals[0],vals[1]));
}
return result;
}
使用最小堆存储候选数对,每次得到一个最小数对
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
Queue<int []> minHeap = new PriorityQueue<>((p1,p2)->nums1[p1[0]] + nums2[p1[1]] - nums1[p2[0]] - nums2[p2[1]]);//存入的是下标,比较的是数对和的大小
//存入下标为(0,0)~(k,0)的数对
if(nums2.length > 0){
for(int i = 0;i < Math.min(k,nums1.length);i++){
minHeap.offer(new int[]{i,0});
}
}
List<List<Integer>> result = new ArrayList<>();
while(k-- > 0 && !minHeap.isEmpty()){
int[] ids = minHeap.poll();
result.add(Arrays.asList(nums1[ids[0]],nums2[ids[1]]));
if(ids[1] < nums2.length - 1){
minHeap.offer(new int[]{ids[0],ids[1]+1});
}
}
return result;
}
小结
通常用最大堆来找出数据集合中的k个最小值,用最小堆来找出数据集合中的k个最大值