前言
堆是一种很常用的数据结构,常常用于排序中,算法的时间复杂度非常的低。本文不再详细介绍堆的概念,只简单回顾一下几个比较重要的知识点,然后做几道LeetCode题目。堆就是一个完全二叉树,每个节点都大于或者小于它的子节点,相应的叫做大顶堆和小顶堆。而完全二叉树可以用数组来表示,可以使用数学公式很轻松的计算出一个节点的父节点和2个子节点,这样就有助于节点值得交换,也就为在堆中增加和删除节点时,进行相应的上浮和下沉操作。
知识点一:往堆中添加元素,元素首先加到数组尾部,也就是完全二叉树的最后一个节点。然后发生上浮操作,如果该节点的值小于父节点值,交换数值,直到不再能够交换为止。
知识点二:从堆中删除元素,每次都是从根节点删除。首先取出根节点的值,然后将最后一个节点的值赋给根节点,接下来就要发生下移操作,每次交换左右孩子中较小的那个,直到换不动为止。
我们可以看到,每次调整节点的位置,都是在一条分支上进行的,所以排序的时间复杂度可以低至O(nlogn)。
正文
Java中util工具类已经给我们实现好了一个堆结构,那就是PriorityQueue优先队列,详细使用方法去看API文档吧。
直接上LeetCode刷几道堆的题吧。
1、No. 1046 最后一块石头的重量
https://leetcode-cn.com/problems/last-stone-weight/
class Solution {
public int lastStoneWeight(int[] stones) {
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (Integer i : stones) {
queue.offer(i);
}
int a = 0;
int b = 0;
while (queue.size() != 1) {
a = queue.poll();
b = queue.poll();
queue.offer(a - b);
}
return queue.peek();
}
}
PriorityQueue默认是小顶堆,可以在构造器里面加一个比较器来变成大顶堆。我们只需要循环往里面添加数据,就能保证优先队列的顺序。
2、No. 剑指Offer 40 最小的k个数
不加比较器,PriorityQueue优先队列就是一个小顶堆,依次往堆里面添加元素,就能构造一个堆,且每次从堆顶移除堆中最小的元素。移除k次,就是最小的k个数。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] ret = new int[k];
PriorityQueue<Integer> priorityQueue = new PriorityQueue();
for (Integer i : arr) {
priorityQueue.offer(i);
}
for (int i = 0; i < k; i ++) {
ret[i] = priorityQueue.poll();
}
return ret;
}
}
3、No. 215 数组中的第K个最大元素
这道题没有说不要重复,那就按顺序找第K个即可。
class Solution {
public int findKthLargest(int[] nums, int k) {
if (nums == null || k < 0 || k > nums.length) {
return -1;
}
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b-a);
for(Integer i : nums) {
pq.offer(i);
}
for(int i = 0; i < k - 1; i++) {
pq.poll();
}
return pq.poll();
}
}
4、No.347 前K个高频元素
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 先统计出每个数字的频率
Map<Integer, Integer> myMap = new HashMap<>();
for (Integer i : nums) {
if (myMap.containsKey(i)) {
myMap.put(i, 1 + myMap.get(i));
} else {
myMap.put(i, 1);
}
}
// 定义一个大顶堆,按照数字的频率排序
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return myMap.get(o2) - myMap.get(o1);
}
});
for (Integer i : myMap.keySet()) {
queue.offer(i);
}
// 最后取出前K个元素即可
int[] ret = new int[k];
for (int i = 0; i < k; i++) {
ret[i] = queue.poll();
}
return ret;
}
}
总结
堆结构很适合求解前K大或者前K小的元素,Java中实现堆结构的是优先队列PriorityQueue,默认小顶堆,也可以使用比较器转换成大顶堆。