LeetCode 热题 100之 堆

news2024/11/24 23:08:56

1.数组中第k个最大元素

在这里插入图片描述
和Acwing 786 第k个数一模一样 排序

思路分析1:此题要求时间复杂度未为O(n)。虽然库函数sort和快速排序都能过,但是时间复杂度不满足条件。下面优化快速排序,写一个快速选择算法。我们可以引入随机化来加速这个过程,它的时间代价的期望是 O(n)。

  • 我们的目标是找到第 k 大的元素。根据索引的定义,第 k 大的元素在从小到大排序后的数组中对应的是第 n - k 小的元素(n 是数组长度)。
  • 调用 quickselect 函数,将 n - k 作为目标索引位置。
  • 辅助函数 quickselect:
    • 如果 l == r,表示区间只剩一个元素,直接返回该元素。
    • 将区间的第一个元素 nums[l] 设为基准值 partition,并初始化两个指针 i 和 j,分别从左向右和从右向左查找。
    • 使用双指针分区法:i 向右查找直到找到第一个大于或等于 partition 的元素,j 向左查找直到找到第一个小于或等于 partition 的元素。如果 i < j,交换 nums[i] 和 nums[j]。
    • 分区结束后,根据 k 的位置选择下一步要递归的区间:如果 k 在 j 的左侧,则递归左侧;否则,递归右侧。

具体实现代码(详解版):

class Solution {
public:
    // Quickselect函数,用于在区间 [l, r] 内找到第 k 小的元素
    int quickselect(vector<int> &nums, int l, int r, int k) {
        // 如果区间只剩一个元素,直接返回该元素
        if (l == r)
            return nums[k];
        
        // 选择区间的第一个元素作为基准值(pivot)
        int partition = nums[l];
        // 初始化左右指针,i 在 l 的前一位,j 在 r 的后一位
        int i = l - 1, j = r + 1;
        
        // 进行分区操作
        while (i < j) {
            // 从左到右找到第一个大于或等于基准值的元素
            do i++; while (nums[i] < partition);
            // 从右到左找到第一个小于或等于基准值的元素
            do j--; while (nums[j] > partition);
            // 如果 i 和 j 没有交叉,交换 nums[i] 和 nums[j]
            if (i < j)
                swap(nums[i], nums[j]);
        }
        
        // 现在 j 是分区位置的边界:所有小于基准值的元素在 j 左边,反之在右边
        
        // 根据 k 的位置选择继续搜索的区间
        if (k <= j) 
            // 如果 k 位于左侧区间,则在左侧递归
            return quickselect(nums, l, j, k);
        else 
            // 如果 k 位于右侧区间,则在右侧递归
            return quickselect(nums, j + 1, r, k);
    }

    // 主函数:找到数组中第 k 个最大的元素
    int findKthLargest(vector<int> &nums, int k) {
        int n = nums.size();
        // 第 k 大的元素在排序后是第 n - k 小的元素(索引从0开始)
        return quickselect(nums, 0, n - 1, n - k);
    }
  • 时间复杂度:
    • 平均情况下,quickselect 的时间复杂度是O(n),因为每次递归都有效缩小了查找区间。
    • 最坏情况下,时间复杂度为 O ( n 2 ) O(n^ 2 ) O(n2),可以通过随机选择基准值来减少最坏情况的发生概率。

思路分析2:利用最小堆(小顶堆)可以让我们高效地找到第 k 大的元素,且时间复杂度接近O(n)。

  • 使用小顶堆:维护一个大小为k的小顶堆。堆顶元素就是当前的第k大元素;
  • 构建堆:priority_queue<int, vector, greater> minHeap;创建一个小顶堆,优先队列默认是大顶堆,使用 greater 转成小顶堆
  • 更新堆
    • 从k+1个元素开始遍历数组
    • 如果当前元素比堆顶元素大,则替换堆顶元素为当前元素,并重新调整堆1。这保证堆中始终保持着当前最大的k个元素
  • 返回结果:遍历完数组后,堆顶元素就是数组中的第k大的元素

具体实现代码(详解版):

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // 创建一个小顶堆,优先队列默认是大顶堆,使用 greater<int> 转成小顶堆
        priority_queue<int, vector<int>, greater<int>> minHeap;
        
        // 将前 k 个元素放入小顶堆
        for (int i = 0; i < k; ++i) {
            minHeap.push(nums[i]);
        }

        // 遍历剩余的元素
        for (int i = k; i < nums.size(); ++i) {
            if (nums[i] > minHeap.top()) { // 如果当前元素比堆顶元素大
                minHeap.pop(); // 移除堆顶元素
                minHeap.push(nums[i]); // 插入当前元素
            }
        }

        // 返回堆顶元素,即第 k 大的元素
        return minHeap.top();
    }
};

2.前k个高频元素

在这里插入图片描述
思路分析1:用排序算法对元素按照频率由高到低进行排序,然后再取前 k 个元素

  • 统计频率:使用哈希表统计每个元素的频率;
  • 转换成频率对:将哈希表中的数据转化为一组(元素,频率)的pair,便于排序;vector<pair<int, int>> freqPairs(freqMap.begin(), freqMap.end());
    -** 排序:使用标准排序算法将这些 pair 按照频率从高到低排序。其中cmp即排序规则可以直接使用lambda表达式
    sort(freqPairs.begin(), freqPairs.end(), [](const pair<int, int>& a, const pair<int, int>& b) { return a.second > b.second; });
  • 提取前 k 个元素:选择排序后的前 k 个元素作为结果。
    具体实现代码(详解版):
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 1. 使用哈希表统计每个元素的频率
        unordered_map<int, int> freqMap;
        for (int num : nums) {
            freqMap[num]++;
        }

        // 2. 将哈希表转换成 (元素, 频率) 的 pair 列表
        vector<pair<int, int>> freqPairs(freqMap.begin(), freqMap.end());

        // 3. 使用标准排序算法对频率对进行排序,按频率从高到低
        sort(freqPairs.begin(), freqPairs.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
            return a.second > b.second;
        });

        // 4. 选择前 k 个元素
        vector<int> result;
        for (int i = 0; i < k; ++i) {
            result.push_back(freqPairs[i].first);
        }
        return result;
    }
};

  • 时间复杂度:总体时间复杂度为 O(n+mlogm),在最坏情况下为 O(nlogn)。
  • 空间复杂度:O(m+k)

思路分析2:可以使用桶排序来解决这个问题,通过将出现频率相同的元素分组到对应的桶中,再按照频率从高到低提取前 k 个元素。

  • 统计频率:使用 unordered_map 统计每个元素的出现频率;
  • 创建桶:创建一个数组(桶)buckets,其中第 i 个桶存储出现频率为 i 的所有元素。数组大小设置为 nums.size() + 1,因为数组中某个元素的最大可能频率不会超过数组的长度。
  • 填充桶:根据每个元素的频率,将其加入到对应频率的桶中。
  • 按频率从高到低提取元素:从频率最高的桶(即 buckets 的末尾)向前遍历,收集桶中的元素,直到收集了 k 个元素为止。

具体实现代码(详解版):

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 1. 统计每个元素的频率
        unordered_map<int, int> freqMap;
        for (int num : nums) {
            freqMap[num]++;
        }

        // 2. 创建桶,桶的索引是频率,桶里存的是具有该频率的所有元素
        int n = nums.size();
        vector<vector<int>> buckets(n + 1); // 每个频率可能出现的次数范围是 0 到 n
        for (auto& [num, freq] : freqMap) {
            buckets[freq].push_back(num);
        }

        // 3. 按频率从高到低收集前 k 个高频元素
        vector<int> result;
        for (int i = n; i >= 0 && result.size() < k; --i) {
            for (int num : buckets[i]) {
                result.push_back(num);
                if (result.size() == k) {
                    break;
                }
            }
        }
        
        return result;
    }
};
  • 时间复杂度:O(n);
  • 空间复杂度:O(n)

3.数据流的中位数

在这里插入图片描述
思路分析:为了实现一个能够动态获取中位数的数据结构 MedianFinder,可以利用两个堆(优先队列)来高效地维护中位数

  • 维护两个堆:使用一个大顶堆和一个小顶堆
    • 大顶堆:用于存储数据流中较小的一般数字,堆顶为较小部分的最大值;
    • 小顶堆:用于存储数据流中较大的一半数字;
  • 中位数的计算
    • 如果数据流的总长度为奇数,maxHeap的堆顶就是中位数;
    • 如果数据流的总长度为偶数,中位数是两个堆顶元素的平均值
  • 调整堆的平衡
    • 每次添加新数字时,将数字插入 maxHeap 或 minHeap 之一,并根据堆的大小调整两者的平衡,以确保 maxHeap 和 minHeap 的元素数量差最多为 1。
    • 如果 maxHeap 的大小大于 minHeap 的大小超过 1,将 maxHeap 堆顶元素移动到 minHeap。
    • 如果 minHeap 的大小大于 maxHeap,将 minHeap 堆顶元素移动到 maxHeap。

具体实现代码(详解版):

class MedianFinder {
private:
    priority_queue<int> maxHeap; // 大顶堆
    priority_queue<int, vector<int>, greater<int>> minHeap; // 小顶堆

public:
    // 初始化
    MedianFinder() {}

    // 添加元素
    void addNum(int num) {
        // 先添加到大顶堆
        maxHeap.push(num);

        // 调整大小:如果 maxHeap 堆顶元素大于 minHeap 堆顶,将它移动到 minHeap
        if (!minHeap.empty() && maxHeap.top() > minHeap.top()) {
            minHeap.push(maxHeap.top());
            maxHeap.pop();
        }

        // 平衡大小:确保 maxHeap 的元素数量不小于 minHeap
        if (maxHeap.size() > minHeap.size() + 1) {
            minHeap.push(maxHeap.top());
            maxHeap.pop();
        } else if (minHeap.size() > maxHeap.size()) {
            maxHeap.push(minHeap.top());
            minHeap.pop();
        }
    }

    // 返回中位数
    double findMedian() {
        // 如果元素总数是奇数,返回 maxHeap 堆顶
        if (maxHeap.size() > minHeap.size()) {
            return maxHeap.top();
        }
        // 如果是偶数,返回两个堆顶的平均值
        return (maxHeap.top() + minHeap.top()) / 2.0;
    }
};
  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2236845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java-智能识别车牌号_基于spring ai和开源国产大模型_qwen vl

用大模型做车牌号识别&#xff0c;最简单高效 在Java场景中&#xff0c;java识别车牌号的需求非常普遍。过去&#xff0c;我们主要依赖OCR等传统方法来实现java识别车牌号&#xff0c;但这些方法的效果往往不稳定。随着技术的发展&#xff0c;现在有了更先进的解决方案——大模…

java ssm 网上蛋糕店 在线蛋糕甜品管理 网上蛋糕管理 源码 jsp

一、项目简介 本项目是一套基于SSM的网上蛋糕店&#xff0c;主要针对计算机相关专业的和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本、软件工具等。 项目都经过严格调试&#xff0c;确保可以运行&#xff01; 二、技术实现 ​后端技术&#xff1a;S…

Qt编译lua库并调用

参考博客&#xff1a; 编译lua库 参考下面文章编译lua库文件 QT5.9学习笔记之QT编译lua库_qtluaintf.h-CSDN博客 https://blog.csdn.net/qq_23345187/article/details/112710677 Qt代码引用lua库文件 打开pro项目文件&#xff0c;右键空白处&#xff0c;点击添加库&#xff…

shopify模块新增内容或图片

1、后台找到指定的liquid页面&#xff0c;在该页面下方{% schema %} 新增需求 2、添加轮播图功能 {% comment %} 轮播代码 {% endcomment %}{% if block.settings.enable_slider %}<divclass"size-guide-slider swiper"data-slides-per-view"{{ block.setti…

【Ant Design Pro】不想用轻量的hook就喜欢用dva的数据状态管理

就像TS是JS的超集一样&#xff0c;antdpro框架也类似&#xff0c;底层也是用dva来构建的。关于数据管理&#xff0c;官方还是建议我们使用轻量的hooks方法来进行操作使用。 使用dva实现数据状态管理效果 框架中的数据管理模式 简单的数据共享 对于简单的应用&#xff0c;不需…

基于 CMSIS-PACK 移植Bootloader

基于 CMSIS-PACK 移植 1.准备工作 准备一份基础的裸机源码 (可通过 STM32CubeMx 可视化软件创建也可按照工程项目所需文档手动创建) 工程&#xff0c;如一份 stm32 包含一个支持 printf 的串口初始化代码。 2.安装Pack包 在 MDK 中部署 **MicroBoot **的第一步是获取对应的…

使用ChatGPT神速精读文献,12个高阶ChatGPT提示词指令,值得你复制使用

在学术研究的道路上,文献的阅读和分析往往是我们迈向深层次理解的第一步。如何有效提取文献中的核心要点,如何全面总结一个研究的背景与贡献,甚至如何深入剖析论文中的每个细节,都是每个研究者必须掌握的技能。通过系统化的文献分析,我们不仅能了解现有研究的框架与成果,…

PICO+Unity MR空间锚点

官方链接&#xff1a;空间锚点 | PICO 开发者平台 注意&#xff1a;该功能只能打包成APK在PICO 4 Ultra上真机运行&#xff0c;无法通过串流或PICO developer center在PC上运行。使用之前要开启视频透视。 在 Inspector 窗口中的 PXR_Manager (Script) 面板上&#xff0c;勾选…

Python Matplotlib 如何绘制股票或金融数据图

Python Matplotlib 如何绘制股票或金融数据图 在金融领域&#xff0c;数据可视化是分析市场趋势、股票表现和财务健康的重要工具。Python 的 Matplotlib 库为我们提供了强大的功能来绘制股票和金融数据图。本文将详细介绍如何使用 Matplotlib 绘制这些图表&#xff0c;并且结合…

Golang--反射

1、概念 反射可以做什么? 反射可以在运行时动态获取变量的各种信息&#xff0c;比如变量的类型&#xff0c;类别等信息如果是结构体变量&#xff0c;还可以获取到结构体本身的信息(包括结构体的字段、方法)通过反射&#xff0c;可以修改变量的值&#xff0c;可以调用关联的方法…

【Web前端】使用 JSON 处理数据

JSON 是一种基于 JavaScript 对象语法的数据格式&#xff0c;由道格拉斯克罗克福特推广。尽管其语法源于 JavaScript&#xff0c;JSON 仍然是独立于 JavaScript 的&#xff0c;这也是为什么许多编程环境能够解析和生成 JSON 的原因。JSON 可以以对象或字符串的形式存在&#xf…

VMware 虚拟机使用教程及 Kali Linux 安装指南

VMware 虚拟机使用教程及 Kali Linux 安装指南 在现代计算机科学与网络安全领域&#xff0c;虚拟化技术的应用越来越广泛。VMware 是一款功能强大的虚拟化软件&#xff0c;可以帮助用户在同一台物理机上运行多个操作系统。本文将详细介绍如何使用 VMware 虚拟机&#xff0c;并…

达梦8数据库适配ORACLE的8个参数

目录 1、概述 1.1 概述 1.2 实验环境 2、参数简介 3、实验部分 3.1 参数BLANK_PAD_MODE 3.2 参数COMPATIBLE_MODE 3.3 参数ORDER_BY_NULLS_FLAG 3.4 参数DATETIME_FMT_MODE 3.5 参数PL_SQLCODE_COMPATIBLE 3.6 参数CALC_AS_DECIMAL 3.7 参数ENABLE_PL_SYNONYM 3.8…

三十四、VB基本知识与提高篇

一、代码编写规则: (一)标识符的使用规则: 标识符有两种:一种是系统关键字,另一种是自己定义标识符。 1、不能与系统关键字相同。 2、同一作用域(块)中不同出现重名标识符。用户自定义的标识符是不区分大小写的。 3、自定义标识符必须以字母开头,长度不能超过255…

数据冒险-ld和add(又称load-use冒险)

第一张图没有使用前递&#xff0c;第二张图使用前递&#xff0c;chatgpt分析第二张图 这张图展示了一个流水线的执行过程&#xff0c;其中存在读后写&#xff08;RAW&#xff09;数据冒险。我们可以通过**前递&#xff08;Forwarding&#xff09;**技术来解决这个数据冒险&…

Coppelia Sim (v-REP)仿真 机器人3D相机手眼标定与实时视觉追踪 (三)

使用标定好的结果进行跟踪标定板的位置 坐标转换的步骤为&#xff1a; 1.图像坐标点转到相机坐标系下的点 2.相机坐标系下的点转为夹爪坐标系下的点 3.夹爪坐标系下的点转为机械手极坐标系下的点 跟踪的方式 1.采用标定板的第一个坐标点作为跟踪点 3.机器人每次移动到该点位&a…

石墨舟氮气柜:半导体制造中的关键保护设备

石墨舟是由高纯度石墨材料制成的&#xff0c;主要用于承载硅片或其他基板材料通过高温处理过程&#xff0c;是制造半导体器件和太阳能电池片的关键设备之一。 石墨舟在空气中容易与氧气发生反应&#xff0c;尤其是在高温处理后&#xff0c;表面可能更为敏感&#xff1b;石墨舟具…

跟着大厂学AI | 智谱AI文本数据提取实践(大模型实战篇)

书接上回理论篇&#xff0c;本文详细介绍LLM处理模块、Prompt 构建、数据抽取后处理、数据校验、数据修复具体实战教程。 想看方案理论教程详见&#xff1a; 跟着大厂学AI | 大模型文本数据提取实践&#xff08;理论篇&#xff09;-CSDN博客文章浏览阅读2次。glm4大模型数据处…

大数据-213 数据挖掘 机器学习理论 - KMeans Python 实现 距离计算函数 质心函数 聚类函数

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

【Pikachu】File Inclusion文件包含实战

永远也不要忘记能够笑的坚强&#xff0c;就算受伤&#xff0c;我也从不彷徨。 1.File Inclusion(文件包含漏洞)概述 File Inclusion(文件包含漏洞)概述 文件包含&#xff0c;是一个功能。在各种开发语言中都提供了内置的文件包含函数&#xff0c;其可以使开发人员在一个代码…