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,也就是上一个比当前数字小的数字
相关推荐
ShineWinsu13 分钟前
对于Qt:QPushButton常见属性的解析
开发语言·c++·qt·api·gui·按钮·qpushbutton
Ivanqhz18 分钟前
MoE + LLM 应用
数据库·人工智能·pytorch·python·深度学习·算法·1024程序员节
Lhan.zzZ22 分钟前
用 Qt 构建线程安全的通讯静态库:从线程亲和性到完整工程
c++·vscode·qt·tcp/ip·udp
西瓜拿铁好喝30 分钟前
开源和闭源 AI 办公工具,分别适合谁
java·前端·数据库
学长毕设32 分钟前
基于SpringBoot的校园礼服租赁系统的设计与实现
java·spring boot·后端
lpfasd12334 分钟前
推荐算法进化史
算法·机器学习·推荐算法
明天也要努力学习38 分钟前
深入理解 JVM 垃圾回收(GC)
java·jvm
xiaoye-duck41 分钟前
《C++ 项目:手写大模型接入 SDK》DeepSeek接口设计(上):初始化和全量响应
开发语言·网络·c++·语言模型
Pniubi44 分钟前
力扣543二叉树的直径(深度优先搜索法(递归))
算法·leetcode·职场和发展
golang学习记1 小时前
IDEA 断点调试技巧:拖拽
java·ide·intellij-idea