【Java完整版 面试必备】Leetcode Top100题目和答案--堆篇

目录

以下摘自leetcode Top100精选题目--堆篇

数组中的第K个最大元素 

前 K 个高频元素

数据流的中位数


以下摘自leetcode Top100精选题目--堆篇

数组中的第K个最大元素 

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例:

输入: [3,2,1,5,6,4], k = 2
输出: 5

Solution:

class Solution {
    // 主函数,接收一个整数数组和一个整数作为参数
    public int findKthLargest(int[] nums, int k) {
        // 使用快速选择算法找到第k个最大的元素
        // 注意,我们需要找到的是第k个最大的元素,但是在快速选择算法中,我们寻找的是第k个最小的元素
        // 所以,我们需要寻找的是第nums.length - k个最小的元素
        return select(nums, 0, nums.length - 1, nums.length - k);
    }

    // 分区函数,用于将数组分为两部分
    private int partition(int[] nums, int left, int right) {
        // 生成一个随机数,用于选择枢轴
        Random rand = new Random();
        int pivotIndex = left + rand.nextInt(right - left + 1);
        
        // 将枢轴的值保存下来
        int pivotValue = nums[pivotIndex];
        
        // 将枢轴移动到数组的末尾
        swap(nums, pivotIndex, right);
        
        // storeIndex用于记录小于枢轴的元素的数量
        int storeIndex = left;
        
        // 遍历数组
        for (int i = left; i < right; i++) {
            // 如果当前的元素小于枢轴
            if (nums[i] < pivotValue) {
                // 将当前的元素和storeIndex位置的元素交换
                swap(nums, i, storeIndex);
                
                // storeIndex向右移动一位
                storeIndex++;
            }
        }
        
        // 将枢轴移动到正确的位置
        swap(nums, storeIndex, right);
        
        // 返回storeIndex,这就是枢轴的正确位置
        return storeIndex;
    }

    // 选择函数,用于找到第k个最小的元素
    private int select(int[] nums, int left, int right, int kSmallest) {
        // 如果left和right相等,那么数组中只有一个元素,这就是我们要找的元素
        if (left == right) { 
            return nums[left]; 
        } 
        
        // 使用分区函数找到枢轴的正确位置
        int pivotIndex = partition(nums, left, right);
        
        // 如果枢轴的正确位置就是我们要找的位置
        if (kSmallest == pivotIndex) {
            // 那么枢轴的值就是我们要找的元素
            return nums[kSmallest];
        } 
        // 如果枢轴的正确位置在我们要找的位置的右边
        else if (kSmallest < pivotIndex) {
            // 那么我们要找的元素在枢轴的左边
            return select(nums, left, pivotIndex - 1, kSmallest);
        } 
        // 如果枢轴的正确位置在我们要找的位置的左边
        else {
            // 那么我们要找的元素在枢轴的右边
            return select(nums, pivotIndex + 1, right, kSmallest);
        }
    }

    // 交换函数,用于交换数组中的两个元素
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

     先定义partition函数,将数组分为两部分,左边的部分都比右边的部分小。定义一个select函数,递归地在数组的一部分中寻找第k个最小的元素。定义swap函数,交换数组中的两个元素。

      在findKthLargest函数中,调用select函数,寻找的是第k个最大的元素,需要寻找的是第nums.length - k个最小的元素。

前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

Solution:

class Solution {
    // 主函数,接收一个整数数组和一个整数k作为参数
    public int[] topKFrequent(int[] nums, int k) {
        // 创建一个哈希表,用于存储每个元素出现的次数
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }

        // 创建一个优先队列,用于存储元素及其出现的次数
        // 优先队列的大小为k,因此它始终只保留出现次数最多的k个元素
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            // 定义比较器,使得优先队列可以按照元素出现的次数进行排序
            public int compare(int[] m, int[] n) {
                return m[1] - n[1];
            }
        });

        // 遍历哈希表中的每个元素及其出现的次数
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            // 如果优先队列的大小等于k
            if (queue.size() == k) {
                // 检查队列顶部的元素的出现次数是否小于当前元素的出现次数
                if (queue.peek()[1] < count) {
                    // 如果是,那么移除队列顶部的元素,并将当前元素添加到队列中
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            } else {
                // 如果优先队列的大小小于k,那么直接将当前元素添加到队列中
                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;
    }
}

数据流的中位数

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 arr = [2,3,4] 的中位数是 3 。
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。

实现 MedianFinder 类:

  • MedianFinder() 初始化 MedianFinder 对象。

  • void addNum(int num) 将数据流中的整数 num 添加到数据结构中。

  • double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。

示例:

输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]

解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1);    // arr = [1]
medianFinder.addNum(2);    // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3);    // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0

Solution:

import java.util.PriorityQueue;

class MedianFinder {
    // 创建两个堆,一个最大堆用于存储较小的一半数,一个最小堆用于存储较大的一半数
    PriorityQueue<Integer> small; // max heap
    PriorityQueue<Integer> large; // min heap

    /** 初始化数据结构 */
    public MedianFinder() {
        small = new PriorityQueue<>((a, b) -> b - a); // 使用lambda表达式创建一个最大堆
        large = new PriorityQueue<>(); // 创建一个最小堆
    }
    
    /** 将数据流中的整数 num 添加到数据结构中 */
    public void addNum(int num) {
        // 如果small堆为空,或者num小于等于small堆的顶部元素,将num添加到small堆中
        if (small.isEmpty() || num <= small.peek()) {
            small.offer(num);
        } 
        // 否则,将num添加到large堆中
        else {
            large.offer(num);
        }
        
        // 平衡两个堆,确保small堆的大小不超过large堆的大小加1,且large堆的大小不超过small堆的大小
        if (small.size() > large.size() + 1) {
            large.offer(small.poll());
        } else if (large.size() > small.size()) {
            small.offer(large.poll());
        }
    }
    
    /** 返回到目前为止所有元素的中位数 */
    public double findMedian() {
        // 如果small堆的大小大于large堆的大小,返回small堆的顶部元素
        if (small.size() > large.size()) {
            return small.peek();
        } 
        // 否则,返回small堆的顶部元素和large堆的顶部元素的平均值
        else {
            return (small.peek() + large.peek()) / 2.0;
        }
    }
}

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何遇mirror

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值