当前位置: 首页 > news >正文

【高频面试题】数组中的第K个最大元素(堆、快排进阶)

文章目录

    • 数组中的第K个最大元素
      • 题目描述
      • 示例1
      • 示例2
      • 提示:
    • 解法1(堆维护前k大元素)
    • 解法2 手写堆维护
    • 解法3(快速选择算法)
    • 例题:P1923 【深基9.例4】求第 k 小的数
    • 参考

数组中的第K个最大元素

题目描述

给定整数数组 n u m s nums nums和整数 k k k,请返回数组中第 k k k 个最大的元素。
请注意,你需要找的是数组排序后的第 k k k 个最大的元素,而不是第 k k k 个不同的元素。
你必须设计并实现时间复杂度为 O ( n ) O(n) 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 < = n u m s . l e n g t h < = 10 5 1 <= k <= nums.length <= 10^5 1<=k<=nums.length<=105
  • − 10 4 < = n u m s [ i ] < = 10 4 -10^4 <= nums[i] <= 10^4 104<=nums[i]<=104

解法1(堆维护前k大元素)

时间复杂度 O ( n l o g k ) O(nlogk) Onlogk 空间复杂度 O ( k ) O(k) Ok)

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int, vector<int>, greater<int>> pq; for(auto& num: nums){pq.emplace(num);if(pq.size() > k){pq.pop();   // 堆中元素超过k个,弹出最小的那个}}return pq.top();   }
};

解法2 手写堆维护

思路:

  • n u m s nums nums种存放二叉堆,索引 [ 0 , n − 1 ] [0,n - 1] [0,n1]对应按层序遍历对应的元素,对于下标从0开始的某节点 i i i,左右孩子节点编号分别为 i ∗ 2 + 1 , i ∗ 2 + 2 i*2+1,i*2+2 i2+1,i2+2
  • 下沉操作: m a x H e a p i f y maxHeapify maxHeapify操作为将二叉堆数组 n u m s nums nums索引 i i i处元素下沉
  • 建堆操作:我们从最后一个非叶子节点 h e a p S i z e / 2 − 1 heapSize / 2-1 heapSize/21开始倒序遍历,从下往上下沉
  • 删除操作:每次将堆顶交换到数组末尾,再将 h e a p S i z e heapSize heapSize减一,最后再调整新的堆顶即可
  • 时间复杂度 O ( n l o g n ) O(nlogn) Onlogn 空间复杂度 O ( l o g n ) O(logn) Ologn) 因为最大堆需要维护n个结点
class Solution {
public:// down void maxHeapify(vector<int>& a, int i, int heapSize) {int l = i * 2 + 1, r = i * 2 + 2, largest = i;if (l < heapSize && a[l] > a[largest]) {largest = l;}if (r < heapSize && a[r] > a[largest]) {largest = r;}if (largest != i) {swap(a[i], a[largest]);maxHeapify(a, largest, heapSize);}}// initialize void buildMaxHeap(vector<int>& a, int heapSize) {for (int i = heapSize / 2 - 1; i >= 0; i--) {maxHeapify(a, i, heapSize);}}int findKthLargest(vector<int>& nums, int k) {int heapSize = nums.size();buildMaxHeap(nums, heapSize); // 建堆// 执行k-1次提取最大值操作for (int i = nums.size() - 1; i >= nums.size() - k + 1; i--) {swap(nums[0], nums[i]);   // 调整堆顶-- heapSize;              // 删除堆maxHeapify(nums, 0, heapSize); }return nums[0];}
};

解法3(快速选择算法)

我们首先来回顾一下快排的实现

void quickSort(vector<int>& nums, int l, int r) {if (l >= r) return;// - i 初始在左边界左侧(l-1)// - j 初始在右边界右侧(r+1)// - 基准值 x 选中间元素(避免极端情况如全排序数组导致最坏时间复杂度)int i = l - 1, j = r + 1;int x = nums[(l + r) >> 1]; // 位运算代替 (l + r) / 2,等价且更高效// partition :双指针从两端向中间移动while (i < j) {// 移动左指针 i:跳过所有小于 x 的元素,直到找到 >=x 的元素do i++; while (nums[i] < x); // 移动右指针 j:跳过所有大于 x 的元素,直到找到 <=x 的元素do j--; while (nums[j] > x);//  如果指针未交错,交换左右指针的元素(确保左边 <=x,右边 >=x)if (i < j) swap(nums[i], nums[j]);}// 4. 递归排序左右子数组:quickSort(nums, l, j), quickSort(nums, j + 1, r);
}
  • 快速选择( Q u i c k s e l e c t Quickselect Quickselect)算法是一种用于在未排序的列表中找到第k小(或第k大)元素的高效算法。与快速排序一样,它使用分治策略,但不同于快速排序的是,它只递归处理包含目标元素的那一部分,而不是全部。这使得快速选择的平均时间复杂度为 O ( n ) O(n) O(n),最坏情况为 O ( n 2 ) O(n^2) O(n2),但在实际应用中,通过随机化选取枢轴( p i v o t pivot pivot)可以避免最坏情况。
  • 复杂度:递归时,每层时间复杂度为 O ( n ) O(n) O(n),但并不是都进入左右两部分递归。仅进入一侧递归在平均情况下 数组长度会减半,故平均情况下的时间复杂度为 n + n / 2 + n / 4 + … + 1 = O ( n ) n+n/2+n/4+…+1=O(n) n+n/2+n/4++1=O(n)

以下是快速选择实现找第K大元素的具体实现:

class Solution {
public:int quick_select(vector<int>& nums, int l, int r, int k) {if (l == r) return nums[k];int i = l - 1, j = r + 1, x = nums[l + r >> 1]; // 若选取nums[l], 极端样例 时间会很久//int x = nums[rand() % (r - l + 1) + l], i = l - 1, j = r + 1; // 随机选取while (i < j) {do i ++ ; while (nums[i] > x); // 注意是第k大 上面的模板是升序排序do j -- ; while (nums[j] < x);if (i < j) swap(nums[i], nums[j]);}if (k <= j) return quick_select(nums, l, j, k);else return quick_select(nums, j + 1, r, k);}int findKthLargest(vector<int>& nums, int k) {//srand(time(0)); // 随机种子return quick_select(nums, 0, nums.size() - 1, k - 1); // 0-base}
};

例题:P1923 【深基9.例4】求第 k 小的数

#include <bits/stdc++.h>using namespace std;const int N = 5e6 + 7;int n, k;
int a[N];int quick_select(int nums[], int l, int r, int k) {if (l == r) return nums[k];int x = nums[l], i = l - 1, j = r + 1;while (i < j) {// paration 方向改一下即可do i ++; while (nums[i] < x); do j --; while (nums[j] > x);if (i < j) swap(nums[i], nums[j]);}if (k <= j) return quick_select(nums, l, j, k);else return quick_select(nums, j + 1, r, k);
}int main() {scanf("%d%d", &n, &k);//getchar();for (int i = 0; i < n; i++) {scanf("%d", &a[i]);}printf("%d\n", quick_select(a, 0, n - 1, k));return 0;
}

参考

  • LeetCode官方题解:数组中的第K个最大元素
  • yxc常用代码模板1——基础算法
  • LeetCode 215. 数组中的第K个最大元素
http://www.xdnf.cn/news/712765.html

相关文章:

  • 动态设置微信小程序页面标题(navigationBarTitleText属性)
  • MATLAB 横向剪切干涉系统用户界面设计及其波前重构研究
  • 记录一次发生的OOM异常,OutOfMemoryError: Java heap space
  • 【笔记】suna部署之获取 OpenRouter API key
  • DFS:从入门到进阶的刷题指南
  • solidworks报错-只有合并特征才能被阵列。如果恰当,请选择实体的阵列
  • 表里不一的程序世界和物理世界
  • Linux日志管理
  • 【LangChain】
  • CAN通信波特率异常的危害
  • 用Python绘制动态爱心:代码解析与浪漫编程实践
  • 进行性核上性麻痹健康护理全指南:从症状管理到生活照护
  • 杏仁海棠花饼的学习日记第十四天CSS
  • 亡羊补牢与持续改进 - SRE 的安全日志、审计与事件响应
  • 树莓派超全系列教程文档--(52)如何启用VNC功能
  • electron安装报错处理
  • K M G T P E Z
  • ChatGPT Plus/Pro 订阅教程(支持支付宝)
  • opengauss 数据库安装主备 非om方式
  • 11 java语言执行浅析1
  • spring boot 拦截器HandlerInterceptor 不生效的原因排查
  • TripGenie:畅游济南旅行规划助手:个人工作纪实(二十一)
  • Shortest path 代码
  • RV1126-OPENCV 交叉编译
  • vue发版html 生成打包到docker镜像进行发版
  • STM32F103_Bootloader程序开发05 - Keil修改生成文件的路径与文件名,自动生成bin格式文件
  • Unity3D仿星露谷物语开发55之保存游戏到文件
  • ubuntu20.04编译 pjproject-2.7.1
  • 删除并重新排队
  • Redis 主从复制中的全量拷贝机制详解