Leetcode 刷题记录 17 —— 堆

本系列为笔者的 Leetcode 刷题记录,顺序为 Hot 100 题官方顺序,根据标签命名,记录笔者总结的做题思路,附部分代码解释和疑问解答,01~07为C++语言,08及以后为Java语言。

01 数组中的第K个最大元素

方法一:快速排序 + 二分递归

java 复制代码
class Solution {
    public int myFunction(int[] nums, int l, int r, int k){
        if(l == r){
            return nums[k];
        }

        int x = nums[l]; //标杆元素
        int i = l - 1, j = r + 1; //双指针

        while(i < j){
            do i++; while(nums[i] < x);
            do j--; while(nums[j] > x);
            if(i < j){
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }

        /**
            j 为标杆,左侧为nums[l...j],右侧为nums[j+1...r] 
         */
        if(k <= j){
            return myFunction(nums, l, j, k);
        }else{
            return myFunction(nums, j+1, r, k);
        }
    }

    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        return myFunction(nums, 0, n-1, n-k);
    }
}

02 前K个高频元素

方法一:小根堆

java 复制代码
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int num : nums){
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
            public int compare(int[] m, int[] n){
                return m[1] - n[1];
            }
        });

        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            int num = entry.getKey();
            int count = entry.getValue();

            if(queue.size() == k){
                if(queue.peek()[1] < count){
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            }else{
                queue.offer(new int[]{num, count});
            }
        }

        int[] ret = new int[k];
        for(int i=0; i<k; i++){
            ret[i] = queue.poll()[0];
        }
        return ret;
    }
}

03 数据流的中位数

java 复制代码
class MedianFinder {

    public MedianFinder() {
        
    }
    
    public void addNum(int num) {
        
    }
    
    public double findMedian() {
        
    }
}

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

方法一:大顶堆 + 小顶堆

左边大顶堆,右边小顶堆;

小的加左边,大的加右边;

平衡俩堆数,新加就弹出,堆顶给对家;

奇数取多的,偶数取除2

java 复制代码
class MedianFinder {
	PriorityQueue<Integer> minHeap;
	PriorityQueue<Integer> maxHeap;

    public MedianFinder() {
        minHeap = new PriorityQueue<Integer>(); // 小顶堆
        maxHeap = new PriorityQueue<Integer>((x, y) -> y - x);// 大顶堆
    }

    public void addNum(int num) {
        if (minHeap.isEmpty() || num > minHeap.peek()) {
            minHeap.offer(num);
        } else {
            maxHeap.offer(num);
        }

        if (minHeap.size() > maxHeap.size() + 1) {
            maxHeap.offer(minHeap.poll());
        } else if (maxHeap.size() > minHeap.size()) {
            minHeap.offer(maxHeap.poll());
        }
    }

    public double findMedian() {
        if (minHeap.size() == maxHeap.size()) {
            return (minHeap.peek() + maxHeap.peek()) / 2.0;
        } else {
            return minHeap.peek();
        }
    }
}

蓝猫淘气三千问

(x, y) -> y - x 是啥意思?

这个写法是 Java 里的Lambda表达式 ,用于实现一个匿名的比较器(Comparator),PriorityQueue 默认是小顶堆 ,即堆顶是最小元素,我们希望实现的是大顶堆 ,即堆顶是最大元素。x - y 实现默认的小顶堆排序,数字小的优先,y - x 交换了顺序,实现反向排序,数字大优先。

if (minHeap.isEmpty() || num > minHeap.peek()) 为什么这样判断?

如果 minHeap 为空(比如刚开始没数据),直接放进 minHeap,这样做方便后面取中位数,如果 num 比当前minHeap堆顶的数(即较大一半中最小的数)还大,说明num属于数字序列的后半部分,应该放入存放大数的minHeap,否则,num更小,属于前半部分,放到maxHeap

return minHeap.peek(); 为什么能直接返回?

代码中是保证 minHeap.size() >= maxHeap.size(),且 minHeap 最大比 maxHeap 多1个元素,当元素个数为奇数时,额外的那个元素存在 minHeap中。

方法二:有序集合 + 双指针

java 复制代码
class MedianFinder {
    TreeMap<Integer, Integer> nums;
    int n;
    int[] left;
    int[] right;

    public MedianFinder() {
        nums = new TreeMap<Integer, Integer>();
        n = 0;
        left = new int[2];
        right = new int[2];
    }
    
    public void addNum(int num) {
        nums.put(num, nums.getOrDefault(num, 0) + 1);
        if (n == 0) {
            left[0] = right[0] = num;
            left[1] = right[1] = 1;
        } else if ((n & 1) != 0) {
            if (num < left[0]) {
                decrease(left);
            } else {
                increase(right);
            }
        } else {
            if (num > left[0] && num < right[0]) {
                increase(left);
                decrease(right);
            } else if (num >= right[0]) {
                increase(left);
            } else {
                decrease(right);
                System.arraycopy(right, 0, left, 0, 2);
            }
        }
        n++;
    }

    public double findMedian() {
        return (left[0] + right[0]) / 2.0;
    }

    private void increase(int[] iterator) {
        iterator[1]++;
        if (iterator[1] > nums.get(iterator[0])) {
            iterator[0] = nums.ceilingKey(iterator[0] + 1);
            iterator[1] = 1;
        }
    }

    private void decrease(int[] iterator) {
        iterator[1]--;
        if (iterator[1] == 0) {
            iterator[0] = nums.floorKey(iterator[0] - 1);
            iterator[1] = nums.get(iterator[0]);
        }
    }
}
代码 含义
System.arraycopy(right, 0, left, 0, 2); 把right数组的两个元素完全复制给left数组
nums.ceilingKey(iterator[0] + 1); 找到TreeMap中 >= 当前数字 + 1 的最小key,也就是下一个比当前数字大的数字
nums.floorKey(iterator[0] - 1); 找到TreeMap中 <= 当前数字 - 1 的最大key,也就是上一个比当前数字小的数字
相关推荐
良木生香几秒前
【C++初阶】C++入门相关知识(2):输入输出 & 缺省参数 & 函数重载
开发语言·c++
小此方1 分钟前
Re:从零开始的 C++ 进阶篇(三)彻底搞懂 C++ 多态:虚函数、虚表与动态绑定的底层原理
c++
计算机毕设vx_bysj68692 分钟前
【免费领源码】77196基于java的手机银行app管理系统的设计与实现 计算机毕业设计项目推荐上万套实战教程JAVA,node.js,C++、python、大屏数据可视化
java·mysql·智能手机·课程设计
忘梓.2 分钟前
墨色规则与血色节点:C++红黑树设计与实现探秘
java·开发语言·c++
hhh3u3u3u2 分钟前
Visual C++ 6.0中文版安装包下载教程及win11安装教程
java·c语言·开发语言·c++·python·c#·vc-1
AI应用实战 | RE3 分钟前
012、检索器(Retrievers)核心:从向量库中智能查找信息
人工智能·算法·机器学习·langchain
凤年徐5 分钟前
C++手撕红黑树:从0到200行,拿下STL map底层核心
c++·后端·算法
星河耀银海5 分钟前
C++ 模板进阶:特化、萃取与可变参数模板
java·开发语言·c++
Thomas.Sir8 分钟前
AI 医疗之罕见病/疑难病辅助诊断系统从算法到实现【表型驱动与知识图谱推理】
人工智能·算法·ai·知识图谱
cccccc语言我来了9 分钟前
【C++---unordered_set/map底层封装】个不拘一格的集合。它不似有序集合那般循规蹈矩,而是以一种洒脱不羁的方式,将元素们随意地散落其中。每一个元素都是独一无二的。
开发语言·c++·哈希算法