首页 > 编程语言 >每日算法一练:剑指offer——数组篇(4)

每日算法一练:剑指offer——数组篇(4)

时间:2024-10-23 12:20:35浏览次数:9  
标签:大顶 maxHeap offer 元素 中位数 addNum 算法 数组 minHeap

数据流中的中位数

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

例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。

示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

提示:

  • 最多会对 addNum、findMedian 进行 50000 次调用。

解题思路

        在这个问题中,我们需要支持两个操作:添加数字和找到当前数字的中位数。由于数据流是动态的,我们不能简单地对所有数字进行排序来找到中位数,因为这在性能上是不可行的,特别是在有大量数据时。

        为了高效地维护中位数,我们可以使用 两个堆(大顶堆和小顶堆)的结构来管理数据:

  1. 大顶堆(Max Heap):用来存储较小的一半元素。这样,堆顶的元素就是这部分元素中的最大值。
  2. 小顶堆(Min Heap):用来存储较大的一半元素。这样,堆顶的元素就是这部分元素中的最小值。

核心思想

  • 堆的大小关系
    • 大顶堆的大小可以比小顶堆多 1,或者两者大小相等。
    • 这确保了我们可以快速找到中位数:
      • 如果大顶堆的大小大于小顶堆,当前中位数就是大顶堆的堆顶元素。
      • 如果两者大小相等,中位数是两个堆顶元素的平均值。

算法流程

以下是实现中位数查找的详细算法流程:

  1. 初始化

    • 创建一个大顶堆 maxHeap 用于存储较小的一半元素。
    • 创建一个小顶堆 minHeap 用于存储较大的一半元素。
  2. 添加数字addNum(int num) 方法):

    • 将新数字添加到大顶堆 maxHeap
    • 检查并保持堆的平衡性:
      • 如果 maxHeap 的堆顶元素大于 minHeap 的堆顶元素,则将 maxHeap 的堆顶元素移到 minHeap
    • 确保两个堆的大小关系:
      • 如果 maxHeap 的大小超过 minHeap 的大小 + 1,则将 maxHeap 的堆顶元素移到 minHeap
      • 如果 minHeap 的大小超过 maxHeap,则将 minHeap 的堆顶元素移到 maxHeap
  3. 查找中位数findMedian() 方法):

    • 如果 maxHeap 的大小大于 minHeap,返回 maxHeap 的堆顶元素。
    • 如果 maxHeapminHeap 的大小相等,返回两个堆顶元素的平均值。

代码实现

import java.util.PriorityQueue; // 引入优先队列类

class MedianFinder {
    Queue<Integer> A, B; // 定义两个队列,A 为小顶堆,B 为大顶堆

    // 构造函数,初始化两个堆
    public MedianFinder() {
        A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
        B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
    }

    // 添加数字
    public void addNum(int num) {
        // 判断当前两个堆的大小是否相等
        if (A.size() != B.size()) {
            // 如果不相等,将新数字添加到小顶堆 A
            A.add(num);
            // 将 A 的堆顶元素(当前最小的较大的一半)移到大顶堆 B
            B.add(A.poll());
        } else {
            // 如果相等,将新数字添加到大顶堆 B
            B.add(num);
            // 将 B 的堆顶元素(当前最大的较小的一半)移到小顶堆 A
            A.add(B.poll());
        }
    }

    // 查找中位数
    public double findMedian() {
        // 如果 A 的大小大于 B,则中位数为 A 的堆顶元素
        return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0; // 返回两个堆顶元素的平均值
    }
}

复杂度分析

  • 时间复杂度

    • addNum(int num):O(log N),因为在堆中插入或移除元素的时间复杂度是对数级别。
    • findMedian():O(1),因为我们直接访问堆顶元素。
  • 空间复杂度:O(N),因为我们需要存储所有输入的数字。

总结

        本题旨在设计一个支持动态数据流中中位数计算的数据结构。我们通过使用两个堆(小顶堆和大顶堆)来实现这一目标。这种方法不仅能够高效地添加新数字,还能快速地找到当前的中位数。小顶堆用于存储较大的一半元素,而大顶堆用于存储较小的一半元素。通过维护这两个堆的大小关系,我们可以在 O(log N) 的时间复杂度内实现添加操作,同时在 O(1) 的时间复杂度内计算中位数。

        具体而言,当新数字被添加时,我们会将其放入适当的堆中,并确保堆的大小关系正确,从而使得中位数可以从堆顶元素中快速获取。这种方法的优势在于能够处理大量的插入和查询操作,而不需要频繁地对整个数据集进行排序。

        这种基于堆的数据结构不仅简洁高效,也展示了如何通过合理的设计在算法性能上实现优化,适用于需要动态更新的统计计算场景。

标签:大顶,maxHeap,offer,元素,中位数,addNum,算法,数组,minHeap
From: https://blog.csdn.net/m0_53926113/article/details/143179837

相关文章

  • KMP数组!启动!
    ......
  • SpringBoot-基于DFA算法实现敏感词过滤
    基于DFA实现敏感词过滤笔记部分来源自黑马程序员DFA全称为:DeterministicFiniteAutomaton,即确定有穷自动机。存储:一次性的把所有的敏感词存储到了多个map中,就是下图表示这种结构敏感词:冰毒、大麻、大坏蛋检索的过程开始实现1、创建数据库表CREATETABLE`sensit......
  • 代码随想录算法训练营第八天|leetcode344.反转字符串、leetcode541. 反转字符串II、卡
    1leetcode344.反转字符串题目链接:344.反转字符串-力扣(LeetCode)文章链接:代码随想录视频链接:字符串基础操作!|LeetCode:344.反转字符串_哔哩哔哩_bilibili自己的思路:直接使用python的内置函数reverse进行一个操作1.1自己的代码1.1.1python的内置函数classSolution:......
  • 金融风控算法--算法就业的另一选择?
    1.背景一聊起算法,很多人便会想到神经网络、深度学习这些,那算法相关的行业领域,那便只有图像处理、文字识别、搜广推几类,但其实风控算法未尝不是一个工作选择的好赛道。从最早的信贷活动开始,从业者一直使用基于人工经验的方法来评估风险。然而,随着20世纪50年代美国信用卡的迅......
  • 15章5节:实现k-medoids聚类算法的PAM和CLARA方法
    K-medoids算法是一种经典的聚类算法,与K-means类似,都是基于划分的方法。然而,K-medoids通过选择数据中的实际数据点作为簇的中心点,在对抗异常值和噪声方面表现出色。本文将介绍k-medoids算法的实现,包括 PAM(PartitioningAroundMedoids)和CLARA(ClusteringLARgeApplications)方......
  • 马拉车算法(C/C++)
    #1024程序员节|征文#马拉车算法(Manacher'sAlgorithm)是一种用于在字符串中查找最长回文子串的线性时间复杂度算法。该算法由UdiManacher在1980年代提出,因此得名。它的核心思想是利用已知的回文信息来减少不必要的比较,从而提高效率。算法步骤预处理字符串:为了处理奇数......
  • 算法复杂度
    文章目录一、算法复杂度的概念二、时间复杂度1.大O的渐进表示法三、空间复杂度提示:以下是本篇文章正文内容,下面案例可供参考一、算法复杂度的概念算法在编写成可执⾏程序后,运⾏时需要耗费时间资源和空间(内存)资源。因此衡量⼀个算法的好坏,⼀般是从时间和空间两......
  • 代码随想录算法训练营day22和day23 | 77. 组合 216.组合总和III 17.电话号码的字母
    学习资料:https://programmercarl.com/回溯算法理论基础.html回溯法backtracking:for循环控制递归数量,暴力搜索:组合、切割、子集、排列、棋盘今天学了组合和切割可以画个N叉树的图来帮助理解回溯过程组合又包括1.单个数组(要加startIndex参数)或多个数组;2.数组内有无重复元素;3.数......
  • Spark实现PageRank算法
    详细步骤:1、创建Sparksql环境2、读取数据3、数据切分(分为page列,outLink列)形成表pageDF4、新增pr一列 (给定初始值)  形成表initPrDF5、新增avgPr一列(根据出链关系,求每个页面所分到的Pr)6、分组聚合(将outLink列explode炸开,在按照page分组,然后sum求和,这就是表......
  • 排序算法 —— 快速排序(理论+代码)
    目录1.快速排序的思想2.快速排序的实现hoare版挖坑法前后指针法快排代码汇总3.快速排序的优化三数取中小区间优化三路划分4.快速排序的非递归版本5.快速排序总结1.快速排序的思想快速排序是一种类似于二叉树结构的排序方法。其基本思想为从待排序序列中任取一个......