设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:

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

比较替换堆顶的数时,不需要让堆顶与数组的每一个数再进行比较,比较数组减去k个数之后剩下的数就行。

1.堆排序方法

// 选出最小的K个数,要建一个大堆// 大堆的向下调整算法
void HeapDown(int* heap, int k, int parent) {// 1.根据父亲结点得到左孩子节点下标childint child = parent * 2 + 1;// 2.进入while循环进行向下调整(while的循环条件是child<k)while (child < k) {// 2.1比较左孩子和右孩子的值,如果右孩子更大,child++,注意要避免越界child+1<kif (child + 1 < k && heap[child] < heap[child + 1]) {child++;}// 2.2如果父亲结点的值小于孩子结点,进行交换,交换后将child结点的下标赋值给父亲结点,//    根据此时的父亲结点计算下一个child的下标if (heap[parent] < heap[child]) {int temp = heap[parent];heap[parent] = heap[child];heap[child] = temp;parent = child;child = parent * 2 + 1;}// 2.3如果父亲结点的值已经大于孩子结点,说明不需要再调整,break跳出循环elsebreak;}
}int* smallestK(int* arr, int arrSize, int k, int* returnSize) {// 0.首先处理两种特殊情况K<=0,K>=arrSzieif (k <= 0) {*returnSize = 0;return NULL;}if (k >= arrSize) {int* result = (int*)malloc(sizeof(int) * arrSize);memcpy(result, arr, sizeof(int) * arrSize);*returnSize = arrSize;return result;}// 1.首先开辟一个空间存我们新建的大堆数据int* heap = (int*)malloc(sizeof(int) * arrSize);// 2.将传入的数组的数据memcpy到我们新建的空间memcpy(heap, arr, sizeof(int) * arrSize);// 3.从下往上进行向下调整构建大堆for (int i = (arrSize - 1 - 1) / 2; i >= 0; i--) {HeapDown(heap, arrSize, i);}int end = arrSize - 1;while (end > 0) {int tem = heap[0];heap[0] = heap[end];heap[end] = tem;end--;HeapDown(heap, end, 0);}int* arrtemp = (int*)malloc(sizeof(int) * k);memcpy(arrtemp, heap, sizeof(int) * k);*returnSize = k;return arrtemp;
}
  1. 边界条件的严谨性
    代码开头对k的特殊情况(k<=0k>=数组长度)做了单独处理:

    • k<=0时,返回空数组并设置returnSize=0
    • k大于等于数组长度时,直接返回原数组。
      这种处理避免了后续无效的堆操作,也防止了数组访问越界,体现了对 “异常输入” 的考虑,是健壮性代码的常见做法。
  2. 堆操作的标准化实现

    • 向下调整算法(HeapDown):这是堆操作的核心,代码正确实现了大堆的向下调整逻辑:
      ① 先比较左右孩子,选择更大的子节点(保证大堆特性);
      ② 若父节点小于子节点,则交换两者,并继续向下调整;
      ③ 若父节点已大于子节点,说明堆已平衡,直接退出。
      其中对child+1 < k的越界检查(避免右孩子不存在时的访问错误),体现了细节处理的严谨性。

    • 堆的构建方式:从最后一个非叶子节点((arrSize-1-1)/2)开始,向上依次调用HeapDown,这是构建堆的标准高效方法(时间复杂度O(n)),比从根节点逐个插入(O(n log n))更优。

  3. 接口设计的规范性
    函数通过returnSize参数返回结果数组的长度,符合 C 语言中 “动态数组需明确长度” 的设计习惯(调用者可通过returnSize正确遍历结果),避免了因长度未知导致的越界访问。

最优解:

// 选出最小的K个数,要建一个大堆// 大堆的向下调整算法
void HeapDown(int* heap, int k, int parent) {// 1.根据父亲结点得到左孩子节点下标childint child = parent * 2 + 1;// 2.进入while循环进行向下调整(while的循环条件是child<k)while (child < k) {// 2.1比较左孩子和右孩子的值,如果右孩子更大,child++,注意要避免越界child+1<kif (child + 1 < k && heap[child] < heap[child + 1]) {child++;}// 2.2如果父亲结点的值小于孩子结点,进行交换,交换后将child结点的下标赋值给父亲结点,//    根据此时的父亲结点计算下一个child的下标if (heap[parent] < heap[child]) {int temp = heap[parent];heap[parent] = heap[child];heap[child] = temp;parent = child;child = parent * 2 + 1;}// 2.3如果父亲结点的值已经大于孩子结点,说明不需要再调整,break跳出循环elsebreak;}
}int* smallestK(int* arr, int arrSize, int k, int* returnSize) {// 0.首先处理两种特殊情况K<=0,K>=arrSzieif (k <= 0) {*returnSize = 0;return NULL;}if (k >= arrSize) {int* result = (int*)malloc(sizeof(int) * arrSize);memcpy(result, arr, sizeof(int) * arrSize);*returnSize = arrSize;return result;}// 1.首先开辟一个空间存我们新建的大堆数据int* heap = (int*)malloc(sizeof(int) * k);// 2.将传入的数组的数据memcpy到我们新建的空间memcpy(heap, arr, sizeof(int) * k);// 3.从下往上进行向下调整构建大堆for (int i = (k - 1 - 1) / 2; i >= 0; i--) {HeapDown(heap, k, i);}// 4.将新建大堆的堆顶值与传入数组剩下的arrSize-k个数据进行比较,如果堆顶的数更大,替换堆顶的数并进行向下调整//j从k开始即可for (int j = k; j < arrSize; j++) {if (heap[0] > arr[j]) {//直接替换即可heap[0]=arr[j];HeapDown(heap, k, 0);}}*returnSize = k;return heap;
}
  1. 大堆的精准应用
    选出 “最小的 K 个数” 时,使用 “大堆” 是非常巧妙的选择:

    • 大堆的堆顶始终是当前 K 个元素中的最大值,便于快速判断新元素是否有资格进入 “最小 K 个” 的集合(只需比较新元素与堆顶)。
    • 若新元素更小,则替换堆顶并调整,保证堆中始终是当前最小的 K 个元素。
      这种思路体现了 “用合适的数据结构解决特定问题” 的设计思想。
  2. 堆构建的高效实现

    • 构建堆时,从最后一个非叶子节点((k-1-1)/2)开始向上调用HeapDown,这是堆初始化的标准高效方法(时间复杂度O(k)),而非从根节点逐个插入(O(k log k))。
    • HeapDown函数的实现严谨:先比较左右孩子取较大值,再与父节点比较交换,确保大堆特性,且对右孩子的越界检查(child+1 < k)避免了数组访问错误。
  3. 内存使用的合理性

    • 堆空间直接分配为k大小(malloc(sizeof(int)*k)),精准匹配需求,不浪费内存。
    • 最终直接返回堆空间作为结果,避免了额外的内存拷贝(上一版中arrtemp的拷贝操作)。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【深度长文】Anthropic发布Prompt Engineering全新指南

目录 1.什么时候适合用提示工程? 2.如何进行提示工程 2.1 使用提示模板 2.1.1 使用提示模板和变量 2.1.2 何时使用提示模板和变量 2.1.3 提示模板示例 2.2 保持清晰和直接 2.2.1 如何保持清晰、具有上下文和具体 2.2.2 示例 ​2.3 使用示例&#xff08;多示例提示…

【基础-判断】HarmonyOS提供了基础的应用加固安全能力,包括混淆、加密和代码签名能力

正确 解释如下: 应用加固: 这是指对应用程序进行保护,使其更难被逆向工程、篡改或盗版。HarmonyOS 作为现代操作系统,确实提供了这样的基础安全能力。 混淆: HarmonyOS 的 SDK 提供了代码混淆工具(通常基于 ProGuard 或类似技术)。开发者在构建应用时启用混淆,可以将类…

HTML 框架:构建网页布局的基石

HTML 框架&#xff1a;构建网页布局的基石 引言 HTML 框架是网页设计中不可或缺的一部分&#xff0c;它为网页内容的布局提供了强大的支持。本文将深入探讨 HTML 框架的概念、种类、应用以及如何有效地使用它们来构建网页布局。 什么是 HTML 框架&#xff1f; HTML 框架是一种网…

[Linux]学习笔记系列 -- [mm][memblock]

文章目录mm/memblock.c: Linux内核的“拓荒时代”内存管理器一、 核心问题&#xff1a;为什么需要 memblock&#xff1f;二、 核心原理与设计三、 在内核启动流程中的角色四、 关键 API五、 总结include/linux/memblock.hmm/memblock.cmemblock_reserve 预留内存块for_each_mem…

Java 面试八股文汇总(1000 道附答案解析)

在过 2 个月即将进入金九银十了&#xff0c;然而面对今年的大环境而言&#xff0c;跳槽成功的难度比往年高了很多&#xff0c;很明显的感受就是&#xff1a;对于今年的 java 开发朋友跳槽面试&#xff0c;无论一面还是二面&#xff0c;都开始考验一个 Java 程序员的技术功底和基…

给纯小白的Python操作 PDF 笔记

一、文件基础打开与关闭 推荐用 with open(path, mode, encodingutf-8) as f:&#xff0c;自动完成 close()&#xff0c;避免泄露文件句柄。常见模式&#xff1a;r 读&#xff0c;w 写覆盖&#xff0c;a 追加&#xff0c;rb/wb 二进制。Windows 默认编码为 GBK&#xff0c;Linu…

vue使用vue-cropper实现图片裁剪之单图裁剪

vue制作的pc系统中(如若依系统)&#xff0c;需要实现按照固定尺寸进行裁剪后再进行图片上传&#xff0c;以下代码讲述的是实现单张图片裁剪上传。1.第一步需要安装vue-croppernpm install vue-cropper2.第二步在需要的页面进入代码引入import {VueCropper} from "vue-crop…

后台管理系统-5-vue3之子路由渲染首页及卡片容器和表格容器实现

文章目录 1 子路由的实现 1.1 router/index.js 1.2 views/Home.vue(首页) 1.3 Main.vue 2 左上方的卡片 2.1 分栏间隔(Layout布局) 2.2 卡片容器(el-card) 2.3 整体代码Home.vue 3 左下方的table(静态实现) 3.1 准备数据 3.2 渲染表格(el-table) 3.3 整体代码Home.vue 4 附录 子…

在CentOS系统中查询已删除但仍占用磁盘空间的文件

在CentOS系统中查询已删除但仍占用磁盘空间的文件在CentOS系统中查询已删除但仍占用磁盘空间的文件1. 检查磁盘整体使用情况2. 查找被删除但仍被进程占用的文件3. 释放磁盘空间4. 替代方案&#xff08;不终止进程&#xff09;注意事项补充工具在CentOS系统中查询已删除但仍占用…

正点原子【第四期】Linux之驱动开发学习笔记-1.1 Linux驱动开发与裸机开发的区别

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子【第四期】手把手教你学Linux系列课程之 Linux驱动开发篇”视频的学习笔记&#xff0c;该课程配套开发板为正点原子alpha/mini Linux开发板。在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内…

Android SystemServer 中 Service 的创建和启动方式

今天导师给我将讲了一些如何新建一个系统服务&#xff0c;以及如何去初始化。 Android SystemServer 中 Service 的创建和启动方式 在 Android 系统中&#xff0c;SystemServer 是系统服务的核心进程&#xff0c;负责启动和管理各种系统服务。以下是 SystemServer 中服务创建和…

SQL SERVER中位数

有11家门店数据&#xff0c;要求每天所有门店的各个指标的中位数1.第一种做法&#xff0c;使用PERCENTILE_CONT&#xff08;&#xff09; 函数 SQL SERVER 2012 版本及以上PERCENTILE_CONT 函数简介PERCENTILE_CONT 是 SQL 中的窗口函数&#xff0c;用于计算连续百分位数&#…

【java中springboot引入geotool】

学习目标&#xff1a; 在Spring Boot项目中引入GeoTools库&#xff0c;可以按照以下步骤进行&#xff1a;理解GeoTools库的基本信息和用途 GeoTools是一个开源的Java库&#xff0c;用于处理地理信息系统&#xff08;GIS&#xff09;数据。它提供了对空间数据的读取、写入、查询…

多项目开发环境:如何使用update-alternatives管理多版本Java JDK?(Windows、Mac、Ubuntu)

如何使用update-alternatives管理多版本Java JDK&#xff1f;&#xff08;Windows、Mac、Ubuntu&#xff09; &#x1f4d6; 摘要 在实际开发中&#xff0c;往往会遇到既要维护老项目又要跟进新特性的场景&#xff0c;这就需要在一台机器上同时安装并切换多个Java JDK版本。本…

力扣57:插入区间

力扣57:插入区间题目思路代码题目 给你一个 无重叠的 &#xff0c;按照区间起始端点排序的区间列表 intervals&#xff0c;其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束&#xff0c;并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval […

KVM虚拟化技术解析:从企业应用到个人创新的开源力量

1 .KVM&#xff1a;开源虚拟化的核心引擎 KVM&#xff08;Kernel-based Virtual Machine&#xff09;作为Linux内核原生集成的开源虚拟化模块&#xff0c;彻底改变了现代数据中心的虚拟化格局。它通过将Linux内核转变为Type-1型虚拟机监控器&#xff08;Hypervisor&#xff09;…

28.Linux :通过源代码编译安装lamp

Linux &#xff1a;通过源代码编译安装lamp 区别特性源代码编译安装yum 安装安装方式从源代码编译构建预编译的二进制包自定义程度高度可定制有限定制性能优化可针对特定硬件优化通用优化依赖管理手动解决依赖关系自动解决依赖安装复杂度复杂&#xff0c;需技术经验简单&#x…

应用控制技术

一、 应用特征识别技术1.传统行为检测技术1.1 五元组检测原理1.2 配置思路1.3 效果展示需求背景21.4 传统行为检测的缺陷无法识别应用层内容&#xff1a;若应用更换端口&#xff08;如QQ改用随机端口&#xff09;或伪装协议&#xff08;如HTTPS加密&#xff09;&#xff0c;传统…

当MySQL的int不够用了

关于int的长度很多时候看到int(8)这样的定义&#xff0c;其实这是工具导出的不专业。int是范围&#xff0c;不是长度。在开发有了共识&#xff08;知道这个长度不算数&#xff0c;要看范围&#xff09;以后&#xff0c;上来就是所有的类型都是bigint。int的范围int的取值范围是…

让AI学会“边做边想“:ReAct的实战指南

小智的求职困境有个叫小智的AI助手&#xff0c;它刚从"大语言模型大学"毕业&#xff0c;满怀信心地去应聘一家咨询公司的智能助理职位。面试官问&#xff1a;"北京和上海哪个城市人口更多&#xff1f;"小智立刻回答&#xff1a;"根据我的知识&#xf…