硅基计划4.0 算法 优先级队列

文章目录


一、最后一块石头的重量

题目链接

这一题说白了就是碰撞石头,我们想想,我们如何利用一种算法求解

我们是不是每一次都要两块最重的石头啊,那是不是我们就可以知道是

你看,如果我们建立大根堆,每次堆顶元素都是最重的石头

写堆排序代码还是非常考验能力的

为什么我们要选择大根堆,因为这一题都是要拿最重的石头,这样我们每次都可以快速选择两块最重的石头

java 复制代码
class Solution {
    public int lastStoneWeight(int[] stones) {
        //使用大根堆,这样保证我们每次队头的两个元素永远是最大值
        PriorityQueue<Integer> heap = new PriorityQueue<>((a,b)->b-a);
        for(int stone : stones){
            heap.offer(stone);
        }
        //比较消除
        while(heap.size() > 1){
            //这样stone1 >= stone2
            int stone1 = heap.poll();
            int stone2 = heap.poll();
            if(stone1 > stone2){
                heap.offer(stone1-stone2);
            }
        }
        return heap.isEmpty() ? 0 : heap.poll();
    }
}

二、数据流中第K大元素

题目链接

这就是典型的TopK问题

如果题目中问我们第K大元素,我们就建立元素个数为K的大根堆

如果题目中问我们第K小元素,我们就建立元素个数为K的小根堆

还有,题目中问的是第K个排序后的元素,如果最终序列是这样2 2 3 3,要第二个最终序列则返回2,如果问的是第二个不重复的最终序列则返回3

当前算法是堆排序,其实还有一个快速选择排序,它时间复杂度可达到O(n)

java 复制代码
class KthLargest {
    //默认是小根堆
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    int size;
    public KthLargest(int k, int[] nums) {
        size = k;
        for(int num : nums){
            heap.offer(num);
            //如果堆的大小超过了k,弹出堆顶元素
            if(heap.size() > k){
                heap.poll();
            }
        }
    }
    
    public int add(int val) {
        heap.offer(val);
        //判断
        if(heap.size() > size){
            heap.poll();
        }
        //注意不能弹出,因为后续插入其他数还要用到
        return heap.peek();
    }
}

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest obj = new KthLargest(k, nums);
 * int param_1 = obj.add(val);
 */

三、前K个高频单词

题目链接

这一题就有点难搞了,首先要保证单词频率是从大到小的,并且还要保证如果单词频次相同按照字段序从小到大

我们可以用一个数对Pair<String,Integer>去存,对于单词建立大根堆,对于频次建立小根堆

具体就是,我们先去统计每个单词出现次数

再去创建一个大小为K的频次小根堆,单词大根堆

循环维护进堆出堆并判断

最终我们的结果是一个逆序的,要记得反转,当然你可以逆着填表最后返回也行

java 复制代码
class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        //预处理字符串,统计出现次数
        HashMap<String,Integer> hash = new HashMap<>();
        for(String str : words){
            hash.put(str,hash.getOrDefault(str,0)+1);
        }
        //对于频次创建小根堆,内部字典序是大根堆
        PriorityQueue<Pair<String,Integer>> heap = new PriorityQueue<>((a,b)->{
            if(a.getValue().equals(b.getValue())){
                //说明两个频次相同,按照字典序排序,如果compareTo结果为负,则b排在前面
                return b.getKey().compareTo(a.getKey());
            }
            //否则就按照正常频次排序
            return a.getValue()-b.getValue();
        });
        //循环进队堆判断
        for(Map.Entry<String,Integer> e : hash.entrySet()){
            heap.offer(new Pair<>(e.getKey(),e.getValue()));
            //判断
            if(heap.size() > k){
                heap.poll();
            }
        }
        //统计结果
        List<String> ret = new ArrayList<>();
        while(!heap.isEmpty()){
            ret.add(heap.poll().getKey());
        }
        //注意返回结果要逆序
        Collections.reverse(ret);
        return ret;
    }
}

四、数据流的中位数------Hard

题目链接

这一题非常不好想,我们看到这题第一眼就是每次插入一个数字后调用库函数sort()一下,但是这样会有O(nlogn)的时间复杂度

再者,我们可以进行插入排序,每次插入一个数都找到位置插入,但是这样要O(n)

那有没有更快的方法呢,有,就是要利用双堆排序思想,也就是大小堆维护中位数

每次我们插入一个数,首先要判断这个数要放去哪,但是在此之前,我们要判断两个堆状态

我们用x表示left大根堆的堆顶元素,用y表示right小根堆的堆顶元素

  • 如果两个堆元素个数相同。若num<=x,此时要进入左边大根堆,满足left元素个数-right元素个数 >=0且<=1;若num > x,此时要进入右边的小根堆,但是此时会导致两边都堆元素个数不一样,怎么办呢?我们可以在插入右边小根堆后,紧接着让right小根堆的堆顶元素放入left大根堆,此时就平衡了
  • 如果两个堆的元素个数不同。若num<=x,此时要进入左边大根堆,但是此时会导致左边大根堆的元素个数不满足要求,一样的,我们插入left大根堆后,让left大根堆的堆顶元素进入right小根堆,此时元素个数达到平衡;若num>x,正常进入right小根堆就好
java 复制代码
class MedianFinder {
    //大根堆
    PriorityQueue<Integer> left = new PriorityQueue<>((a,b)->{
        return b-a;
    });
    //小根堆
    PriorityQueue<Integer> right = new PriorityQueue<>((a,b)->{
        return a-b;
    });
    //我们要始终保持left的元素个数>=right的元素个数且right元素个数不超过left+1
    public MedianFinder(){}
    
    public void addNum(int num) {
        //插入数字的时候进行判断
        if(left.size() == right.size()){
            //判断这个数字进行哪个堆,如果是空堆直接进
            if(left.isEmpty() || num <= left.peek()){
                left.offer(num);
            }else{
                //先让num进右边
                //要让右边堆顶元素放入左边,维持两个堆数量平衡
                right.offer(num);
                left.offer(right.poll());
            }
        }else{
            if(num <= left.peek()){
                //进入左边
                left.offer(num);
                //此时左边堆顶元素要去右边
                right.offer(left.poll());
            }else{
                //反之进入右边
                right.offer(num);
            }
        }
    }
    
    public double findMedian() {
        //如果两个堆规模相等,说明是偶数个列表
        return left.size() == right.size() ? (left.peek()+right.peek())/2.0 : left.peek();
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

感谢你的阅读


END

相关推荐
Swift社区2 小时前
LeetCode 375 - 猜数字大小 II
算法·leetcode·swift
sunfove2 小时前
从“锯齿”到“光滑”:相位解包裹 (Phase Unwrapping) 算法深度解析
算法
DuHz2 小时前
自动驾驶雷达干扰缓解:探索主动策略论文精读
论文阅读·人工智能·算法·机器学习·自动驾驶·汽车·信号处理
漫随流水2 小时前
leetcode算法(257.二叉树的所有路径)
数据结构·算法·leetcode·二叉树
liu****2 小时前
神经网络基础
人工智能·深度学习·神经网络·算法·数据挖掘·回归
有一个好名字2 小时前
力扣-二叉树的最大深度
算法·leetcode·深度优先
Aaron15882 小时前
基于RFSOC 49DR+VU13P的64通道VPX架构波束成形技术分析
c语言·人工智能·算法·架构·信息与通信·信号处理·基带工程
我是一只小青蛙8882 小时前
二分查找巧解数组范围问题
java·开发语言·算法
C_心欲无痕2 小时前
构建工具中的 hash 与 contenthash作用:以 Webpack 和 Vite 为例
算法·webpack·哈希算法