HOT100系列-堆类型题

HOT100系列-堆类型题

核心思想

例题

1、数组中的第K个最大元素

题目描述:

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

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

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104

解题思路:

  • 快排

这里简单介绍下快速排序算法:

普通的快速排序算法首先是要在待排序数组中随机选取一个数记作 num,比 num 小的都放于 num 左边,比 num 大的都放于右边,不断迭代这个过程,直到迭代的数组中左右边界相同为止,具体步骤如下

  • 随机在数组左右边界 l, r 中选择一个数 num
  • 对 (l, r) 内的数组元素进行遍历,这个过程要使比 num 小的元素都在左边,比 num 大的元素都在右边,最后将第一个遇见的 num 置于左与右的中间位置(其实就是 num 在 l, r 的排序后的对应的最后位置,这里假设有多个 num),返回 num 位置的索引 m
  • 在(l, m)和(m+1, r)中执行上述步骤直到待排序数组片段的左边界=右边界(即待排序数组片段只有一个元素)

上述普通快排有一个非常大的问题就是当一个数组片段中有多个 num 时,它只排了其中一个,其余都未排到对应位置

而荷兰国旗的快速排序就对其做了优化,使得将所有 num 都排到了数组片段的对应位置,提高了算法效率

荷兰国旗的优化相对于普通快排最大的地方在于一次将将所有 num 都排到了数组片段的对应位置,在这个排序的过程中如下

已知arr[l... r]范围上一定有 num这个值

  • 划分数组 <x放左边,x放中间,>x放右边
  • 把全局变量first, last,更新成x区域的左右边界
  • 小于num的放first左边,大于num的放last右边
  • 若arr[i] < num 则swap (first, i), first++, i++
  • 若arr[i]>num 则swap (last, i), last--
  • 若arr[i] num 则i++

代码如下:

Java 复制代码
class Solution {

    public static int first;

    public static int last;

    public int findKthLargest(int[] nums, int k) {

        return  randomizedSelect(nums,nums.length-k);

    }

  

    //从num数组中挑选出第k大的元素

    public static int randomizedSelect(int[]nums,int k){

        int ans=0;

        for(int l=0,r=nums.length-1,num;l<=r;){

            //随机选择数字

            num=nums[l+(int)(Math.random()*(r-l+1))];

            //荷兰国旗的快排

            partition(nums,l,r,num);

            //看当前随机数字排序位置

            //有一些二分的思想

            if(first>k){

                //说明第K大的数在num左侧

                //接下来只管排左侧

                r=first-1;

            }else if(last<k){

                //说明第K大的数在num右侧

                //接下来只管排右侧

                l=last+1;

            }else{

                //第K大的数值在first和last之间

                //则由快排确定第K大元素

                ans=nums[k];

                return ans;

            }

        }

        return ans;

    }

  

    //荷兰国旗快排

    //将num数值排在数组正确位置,从小到大

    public static void partition(int[]nums,int l,int r,int num){

        first=l;

        last=r;

        int i=l;

       while(i<=last){

            if(nums[i]<num){

                //first左侧全部放比num小的元素

                swap(nums,i,first);

                first++;

                i++;

            }else if(nums[i]>num){

                swap(nums,i,last);

                last--;

            }else{

                i++;

            }

        }

  

    }

  

    public static void swap(int[] nums,int i,int j){

        int temp=nums[i];

        nums[i]=nums[j];

        nums[j]=temp;

    }

}

2、前 K 个高频元素

题目描述:

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

示例 1:

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

输出:[1,2]

示例 2:

**输入:**nums = [1], k = 1

输出:[1]

示例 3:

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

输出:[1,2]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

**进阶:**你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

解题思路:

  • 收集元素频次
  • 将<元素,元素频次> 放进大根堆中,以元素频次排序
  • 取大顶堆前k个即可

代码如下

java 复制代码
class Solution {

    public int[] topKFrequent(int[] nums, int k) {

        Map<Integer,Integer> map=new HashMap<>();

        int n=nums.length;

        //收集元素的频次

        for(int i=0;i<n;i++){

            map.put(nums[i],map.getOrDefault(nums[i],0)+1);

        }

        //创建大根堆

        PriorityQueue<int[]>heap=new PriorityQueue<>((a,b)->b[1]-a[1]);

        for(int key:map.keySet()){

            heap.offer(new int[]{key,map.get(key)});

        }

  

        int[]ans=new int[k];

        for(int i=0;i<k;i++){

            ans[i]=heap.poll()[0];

        }

        return ans;

    }

}

3、数据流的中位数

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

  • 例如 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 以内的答案将被接受。

示例 1:

输入

"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

提示:

  • -105 <= num <= 105
  • 在调用 findMedian 之前,数据结构中至少有一个元素
  • 最多 5 * 104 次调用 addNumfindMedian

解题思路

  • 1、将较大的元素放入小顶堆中,将较小的元素放入大顶堆中,这样从大顶堆底部到顶部,从小顶堆的顶部到底部就相当于对加入的数据进行了排序
  • 2、在排序的基础上,维持大小顶堆的size之差必须小于2,那么就相当于将中位数永远保持在了大小顶堆的顶部,比较大小顶堆size大小关系,即可找出中位数(具体看findMedian方法)

代码如下

Java 复制代码
class MedianFinder {

    //大根堆,存放较小的数字

    private PriorityQueue<Integer> maxheap;

    //小根堆,存放较大的数字

    private PriorityQueue<Integer> minheap;

  

    public MedianFinder() {

        maxheap=new PriorityQueue<>((a,b)->b-a);

        minheap=new PriorityQueue<>((a,b)->a-b);

    }

    public void addNum(int num) {

        if(maxheap.isEmpty() || maxheap.peek()>=num){

            maxheap.add(num);

        }else{

            minheap.add(num);

        }

        //要注意保持大小根堆的平衡,即size差不能超过2

        banlance();

    }

  

    public void banlance(){

        if(Math.abs(maxheap.size()-minheap.size())==2){

            if(maxheap.size()>minheap.size()){

                minheap.add(maxheap.poll());

            }else{

                maxheap.add(minheap.poll());

            }

        }

    }

    public double findMedian() {

        if(maxheap.size()==minheap.size()){

            return (double)(maxheap.peek()+minheap.peek())/2.0;

        }else{

            return maxheap.size()>minheap.size()?maxheap.peek():minheap.peek();

        }

    }

}
相关推荐
报错小能手1 小时前
数据结构 带头节点的链表
数据结构·链表
Christo31 小时前
ICML-2019《Optimal Transport for structured data with application on graphs》
人工智能·算法·机器学习·数据挖掘
sin_hielo1 小时前
leetcode 1590
数据结构·算法·leetcode
吃着火锅x唱着歌1 小时前
LeetCode 2748.美丽下标对的数目
数据结构·算法·leetcode
做怪小疯子1 小时前
LeetCode 热题 100——二叉树——二叉树的中序遍历
算法·leetcode·职场和发展
一只乔哇噻1 小时前
java后端工程师+AI大模型进修ing(研一版‖day57)
java·开发语言·人工智能·算法·语言模型
晨曦夜月2 小时前
笔试强训day4
算法
自然语2 小时前
人工智能之数字生命-学习的过程
数据结构·人工智能·深度学习·学习·算法
你好~每一天2 小时前
从传统行业到AI入门:我的CAIE Level I学习体验与思考
大数据·数据结构·人工智能·学习·jupyter·idea