算法学习(3):LeetCode刷题之优先队列(堆)

前言

堆是一种很常用的数据结构,常常用于排序中,算法的时间复杂度非常的低。本文不再详细介绍堆的概念,只简单回顾一下几个比较重要的知识点,然后做几道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,默认小顶堆,也可以使用比较器转换成大顶堆。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值