目录

一,题目描述

英文描述

中文描述

示例与说明

二,解题思路

1,手动实现堆——C++泛型实现

2,手动实现堆——java泛型实现

3,快速使用堆——C++

优先队列

pop_heap()、push_heap()

4,快速使用堆——java

三,AC代码

C++

Java

四,解题过程

第一博


一,题目描述

英文描述

English description is not available for the problem. Please switch to Chinese.

中文描述

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例与说明

限制:

  • 0 <= k <= arr.length <= 10000
  • 0 <= arr[i] <= 10000

来源:力扣(LeetCode)
链接:力扣
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二,解题思路

很明显这一题可以采用堆来实现。

通常堆的实现是基于完全二叉树的,也就是说可以创建一个数组,并利用下标定位子节点或父节点。

堆可以分为两种:大根堆(根节点最大),小根堆(根节点最小)。

堆的操作主要分两种(以大根堆为例):

  • pop:将头节点弹出,从左右节点选出较大者填补空缺,迭代完成后续操作。这一过程是自上而下完成的,可以看作sift_down;
  • push:将节点插入到数组末尾,不断和其父节点比较并替换,迭代完成后续操作。这一过程是自下而上完成的,可以看作sift_up;

说起来容易,但是要想实现功能较为完整的堆,并灵活的运用到题目中去,还需要借助泛型编程的力量。

1,手动实现堆——C++泛型实现

参考@AlexanderGan【堆__C++泛型实现简易的优先队列】,这里对算法实现的具体细节略作修改。

#include<iostream>
#include<vector>
#include<algorithm>
#include<limits.h>using namespace std;template<typename T>
// 小根堆,Greater可以理解为后面的数据越来越大
class Greater {
public:// true:参数2优先级高,false:参数1优先级高。与C++中的设计保持一致bool operator() (const T& a, const T&b) {return a > b;}
};template<typename T, typename CMP>
class MyHeap {
private:vector<T> data;private:// 从当前位置pos向下筛选,和子节点中优先级最高的对比,判断是否继续向下void siftDown (int pos) {int i = pos * 2 + 1;                // 左子节点while (i < data.size()) {if (i + 1 < data.size() && CMP()(data[i], data[i + 1])) {i++;                        // 选出优先级较高的位置}if (CMP()(data[pos], data[i]) == false) {// 当前节点优先级高于任意子节点,不需要继续向下判断break;} else {// 选择优先级较高的节点替换当前位置pos,继续向下判断swap(data[i], data[pos]);pos = i;i = pos * 2 + 1;}}}// 从当前位置pos向上筛选,和父节点对比,判断是否继续向上void siftUp (int pos) {// 父节点存在,且当前节点优先级高于父节点while ((pos - 1) / 2 >= 0 && CMP()(data[(pos - 1) / 2], data[pos])) {swap(data[pos], data[(pos - 1) / 2]);pos = (pos - 1) / 2;}}public:T top () {if (data.size() > 0) return data[0];// 堆的下标从0开始return INT_MIN;                     // 用最大或最小值标记非法输出}void push (T x) {data.push_back(x);siftUp(data.size() - 1);}void pop () {data[0] = data[data.size() - 1];    // 把最后一个元素移到数组头部,将其覆盖data.pop_back();                    // 将数组尾部的元素弹出siftDown(0);                        // 自上而下调整各个节点}int size() {return data.size();}};int main() {MyHeap<int, Greater<int> > myHeap;myHeap.push(1); myHeap.push(3);myHeap.push(2);myHeap.push(6);myHeap.push(4);myHeap.push(5);myHeap.push(8);myHeap.push(7);myHeap.push(9);while (myHeap.size()) {cout<<myHeap.top()<<endl;myHeap.pop();}return 0;
}

2,手动实现堆——java泛型实现

C++中可以无脑使用vector来存储数据。Java一般用ArrayList或LinkedList。

  • 由于堆需要频繁的弹出数组中的元素,ArrayList作为连续数组的一种实现方式,显然不是很好的选择(连续数组删除元素后,为保持正确性,需要移动后面的元素来填补空缺);
  • LinkedList虽然可以方便的实现节点的删除,但是无法利用索引位置定位,效率也比较低下;

因此这里直接采用数组形式来实现数据存储,这样需要自己控制数组的容量并记录当前数组的元素数目。

具体实现参考@艾黛尔贾特【使用 Java 实现优先队列(小根堆)】,大佬写的很详细,还包括了扩容算法,这里为了简化代码就没加上这部分。

知识补充

@成长的小菜鸟【接口作为类型使用】https://blog.csdn.net/weixin_35756522/article/details/77016987https://blog.csdn.net/weixin_35756522/article/details/77016987@唯一浩哥【Java基础系列-Comparable和Comparator】https://www.jianshu.com/p/f9870fd05958https://www.jianshu.com/p/f9870fd05958>】" data-link-title="@小米干饭【如何理解 Java 中的 >】">@小米干饭【如何理解 Java 中的 >】https://www.cnblogs.com/xiaomiganfan/p/5390252.htmlhttps://www.cnblogs.com/xiaomiganfan/p/5390252.html

// 类型E或E的父类必须实现Comparable接口中的compareTo方法
public class MyHeap <E extends Comparable<? super E>> {private static final int DEFAUT_CAPACITY = 100;// 设置数组默认大小private int currentSize;// 表示当前堆的大小private E[] data;// 存放数据/*** 构造方法,初始化数组及当前容量*/public MyHeap() {data = (E[]) new Comparable[DEFAUT_CAPACITY];currentSize = 0;}/*** 交换位置i、j对应的节点* @param i* @param j*/private void swap (int i, int j) {E tem = data[i];data[i] = data[j];data[j] = tem;}/*** 将pos位置的节点向上调整* @param pos*/private void siftUp (int pos) {int fatIndex = (pos - 1) / 2;// 从数组下标0开始存放数据,所以计算父节点位置需要先减一// 下标未越界且当前节点优先级高于父节点while (fatIndex >= 0 && data[pos].compareTo(data[fatIndex]) > 0) {swap(pos, fatIndex);pos = fatIndex;fatIndex = (pos - 1) / 2;}}/*** 将pos位置的节点向下调整* @param pos*/private void siftDown (int pos) {int childIndex = pos * 2 + 1;while (childIndex < currentSize) {// 下标未越界且右子节点优先级高于左子节点if (childIndex + 1 < currentSize && data[childIndex + 1].compareTo(data[childIndex]) > 0) {childIndex++;}// 当前节点优先级高于任意子节点,停止向下筛选if (data[pos].compareTo(data[childIndex]) > 0) {break;} else {swap(pos, childIndex);pos = childIndex;childIndex = pos * 2 + 1;}}}public int size() {return currentSize;}public E top() {return currentSize > 0 ? data[0] : null;}public void push(E e) {if (currentSize == DEFAUT_CAPACITY) {System.out.println("数据溢出,请重新设置默认容量");return;}data[currentSize++] = e;siftUp(currentSize - 1);}public void pop() {if (currentSize == 0) {System.out.println("暂无数据");return;}data[0] = data[--currentSize];// 将最后一个元素填充到空出来的位置siftDown(0);// 向下筛选}
}

如何理解C++和Java中的比较器?(参考@蓦子骞【小根堆的建立与比较器】)

  • 如果comparator返回值为false,可理解为operator操作无效,a和b的顺序和形参表中一样,a依旧在前,b在后;
  • 若返回值为true, operator操作有效,交换ab位置,b在前(即更“大”);

3,快速使用堆——C++

主要有两种方法,一种是直接使用优先队列,另一种是借助pop_heap()、push_heap()实现。

优先队列

参考@AAMahone【C++ priority_queue的自定义比较方式】

优先队列的这个类型,其实有三个参数:priority_queue<class Type,class Container,class Compare>,即类型,容器和比较器,后两个参数可以缺省,这样默认的容器就是vector,比较方法是less,也就是默认大根堆(less对应大根堆!!!表示元素越来越小),可以自定义写比较方法,但此时若有比较方法参数,则容器参数不可省略!priority_queue<>的可支持的容器必须是用数组实现的容器,如vector,deque,但不能是list(推荐vector),比较方法可以写结构体重载()运算符,也可以用less,greater这些语言实现了的,但是灵活性不够,建议手写重载结构体,或者——如果不想写比较结构体的话,就将后面的两个参数缺省,直接重载类型的<运算符

priority_queue<int> Q;
for (int i = 0; i < k; ++i) {Q.push(arr[i]);
}
for (int i = k; i < (int)arr.size(); ++i) {if (Q.top() > arr[i]) {Q.pop();Q.push(arr[i]);}
}
// -------------------------------------
struct cmp {bool operator()(node a, node b) {return a.val < b.val;}
};
priority_queue<node, vector<node>, cmp> Q;

pop_heap()、push_heap()

vector<int> nums = { 4, 5, 1, 3, 2 ,8 ,7};
make_heap(nums.begin(), nums.end(),less<int>());
cout << "initial max value : " << nums.front() << endl;
// pop max value
pop_heap(nums.begin(), nums.end());
// push a new value
nums.push_back(6);
push_heap(nums.begin(), nums.end());

4,快速使用堆——java

下面的方法在leetcode上可以直接使用,但是在IDEA中会报错,不晓得是哪里的问题(jdk1.8版本太低了?)

PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {// 大根堆    public int compare(Integer num1, Integer num2) {return num2 - num1;}
});
queue.offer(x);// 相当于push
queue.poll();// 相当于pop
queue.peek();// 相当于top

三,AC代码

C++

class Solution {
public:vector<int> getLeastNumbers(vector<int>& arr, int k) {priority_queue<int, vector<int>, less<int>> heap;vector<int> ans(k);for (int x : arr) {heap.push(x);if (heap.size() > k) heap.pop();}for (int i = 0; i < k; i++) {ans[i] = heap.top();heap.pop();}return ans;}
};

Java

class Solution {public int[] getLeastNumbers(int[] arr, int k) {PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>(){public int compare (Integer num1, Integer num2) {return num2 - num1;}});for (int i = 0; i < arr.length; i++) {heap.offer(arr[i]);if (heap.size() > k) {heap.poll();}}int[] ans = new int[k];for (int i = 0; i < k; i++) {ans[i] = heap.peek();heap.poll();}return ans;}
}

四,解题过程

第一博

优先队列,没什么好说的,记录下手写首先队列的方法,以备万一

LeetCode_Heap_剑指 Offer 40. 最小的k个数 【堆,泛型实现,自定义比较器】【C++/java】【简单】相关推荐

  1. 【LeetCode】剑指 Offer 40. 最小的k个数

    [LeetCode]剑指 Offer 40. 最小的k个数 文章目录 [LeetCode]剑指 Offer 40. 最小的k个数 一.笨比解法 二.堆排序 三.快速选择 总结 一.笨比解法 选择排序变 ...

  2. 【Java】 剑指offer(40) 最小的k个数

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 输入n个整数,找出其中最小的k个数.例如输入4.5.1.6.2.7 ...

  3. 最小的k个数 java_【Java】 剑指offer(40) 最小的k个数

    本文参考自<剑指offer>一书,代码采用Java语言. 题目 输入n个整数,找出其中最小的k个数.例如输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 思 ...

  4. 剑指offer 40.最小的 K 个数 python代码

    题目 寻找数组中的最小的k个数,也叫topk问题. 牛客网测试地址 注意: 牛客网的提交需要将最终的结果排序 思路 快速排序的 partition() 方法,会返回一个整数 j 使得 a[l-j-1] ...

  5. 【力扣刷题】剑指 Offer 40. 最小的k个数(大顶堆)

    题目: 输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 示例: 输入:arr = [3,2,1], k = 2 ...

  6. 剑指 Offer 40. 最小的k个数

    题目 输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 示例 输入:arr = [3,2,1], k = 2 输 ...

  7. Leetcode 剑指 Offer 40. 最小的k个数 (每日一题 20210825)

    输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4.示例 1:输入:arr = [3,2,1], k = 2 输出: ...

  8. 【算法】剑指 Offer 40. 最小的k个数 【重刷】

    1.概述 输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 示例 1: 输入:arr = [3,2,1], k ...

  9. 174. 地下城游戏;剑指 Offer 40. 最小的k个数;378. 有序矩阵中第K小的元素;703. 数据流中的第K大元素

    一些恶魔抓住了公主(P)并将她关在了地下城的右下角.地下城是由 M x N 个房间组成的二维网格.我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主. 骑士的初始 ...

最新文章

  1. 【原】iOS学习之事件处理的原理
  2. 代码重构之三种取代类型码(类、子类、状态对象或策略对象)的方式辨析
  3. android 8.0 Account行为变更 账号系统
  4. 欢迎使用CSDN-markdown编辑器(此为使用指南,自己还不熟练有些功能的使用)
  5. java怎么xml文件解析_Java对Xml文件解析
  6. Lesson 7(12)神经网络的诞生与发展机器学习基本概念
  7. kafka自带没web ui界面,怎么办?安装个第三方的
  8. java垃圾回收机制_干货:Java 垃圾回收机制
  9. ios 筛选_万千网友让quot;低调使用quot;的软件!居然还支持iOS
  10. 【OC底层】OC对象本质,如 isa, super-class
  11. ASP.NETMVC Model验证(五)
  12. 的有效性最好_股票职业玩家教韭菜实战,验证技术指标的有效性,资产增值是王道...
  13. 关于亿图图示缩放锁定1%的问题
  14. SOLIDWORKS教程:solidworks常用技巧大全
  15. uniapp 跳转到商品详情页
  16. 计算机主机电池馈电,电脑主板电池没电了会出现什么情况?电脑主板电池没电的解决方法...
  17. python人工智能面试题爱奇艺面试题_【爱奇艺Python面试】爱奇艺大数据面试 python-看准网...
  18. Windows10常用快捷键+cmd常见命令码
  19. Frame 与JFrame 的区别
  20. IEC61499和PLCopen 运动控制

热门文章

  1. Chrome浏览器命令行菜单实现图片页面保存
  2. 当前的深度学习框架不会改变机器学习的能力增长
  3. 四川一公司元旦春节放假47天!老板称工资照发,把网友羡慕死了
  4. 大数据数仓搭建-大数据用户画像推荐系统搭建
  5. 将项目部署到nginx服务器上
  6. 【天津大学】成立一所国家研究院
  7. 设计数据结构的方法论
  8. 适用于 Windows 的 X 服务器
  9. 2022国际开源节(IOSF)锁定9月,元宇宙产业风向标地位稳了!
  10. 实训一:交换机带外管理(就是用CRT连接交换机)