首页 > 其他分享 >leetcode215. 数组中的第K个最大元素,小根堆/快排思想

leetcode215. 数组中的第K个最大元素,小根堆/快排思想

时间:2024-08-28 20:51:46浏览次数:12  
标签:nums int 复杂度 元素 快排 算法 数组 小根堆 leetcode215

leetcode215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 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 <= nums.length <= 105
-104 <= nums[i] <= 104

目录

题目分析

给定一个整数数组 nums 和一个整数 k,返回数组中第 k 小的元素。这个问题可以通过最小堆(优先队列)/快排思想来解决。

方法一:自建小根堆

在这里插入图片描述

堆(优先队列)的概念介绍

堆的定义

堆是一种特殊的完全二叉树,其中每个父节点的值都必须小于或等于其所有子节点的值(小根堆)或每个父节点的值都必须大于或等于其所有子节点的值(大根堆)。

大根堆和小根堆

  • 大根堆:每个父节点的值都必须大于或等于其所有子节点的值。
  • 小根堆:每个父节点的值都必须小于或等于其所有子节点的值。

数组表示堆

在计算机中,堆通常使用数组来表示。堆的根节点位于数组的第一个位置,数组的索引从0开始。

建堆的过程

堆的构建过程就是从最后一个非叶子结点开始从下往上调整。 最后一个非叶子节点怎么找?这里我们用数组表示待排序序列,则最后一个非叶子结点的位置是:数组长度/2-1。

找前K个最大的数应该用小根堆

  • 原因:使用小根堆可以在O(n)时间内找到第K大的数,这是因为每次从堆顶取出最小的数,直到剩下K个数,堆顶即为第K大的数。
  • 实现:初始化一个大小为K的小根堆,遍历数组,将每个元素与堆顶元素比较,如果小于堆顶元素,则将该元素与堆顶元素交换,并调整堆。重复此过程,直到数组遍历结束。

算法步骤

  1. 初始化一个最小堆 a,大小为 k
  2. 调用 buildMinHeap(a, k) 函数构建最小堆。
  3. 从后往前遍历数组 nums,每次将堆顶元素与当前遍历到的元素比较。
  4. 如果当前元素小于堆顶元素,则将当前元素与堆顶元素交换,然后对堆进行 adjMinHeap 操作。
  5. 重复步骤3和4,直到数组遍历结束。
  6. 堆顶元素即为第 k 小的元素。

算法流程

开始 初始化最小堆a 调用buildMinHeap a, k 遍历nums的每个元素 当前元素小于堆顶元素 交换当前元素和堆顶元素 调用adjMinHeap a, k 返回堆顶元素 结束

具体代码

class Solution {
public:
    void adjMinHeap(vector<int>& nums, int root, int heapsize) {
        int left = root * 2 + 1, right = root * 2 + 2, minimum = root;
        if (left < heapsize && nums[left] < nums[minimum])
            minimum = left;
        if (right < heapsize && nums[right] < nums[minimum])
            minimum = right;
        if (minimum != root) {
            swap(nums[minimum], nums[root]);
            adjMinHeap(nums, minimum, heapsize);
        }
    }

    void buildMinHeap(vector<int>& nums, int k) {
        for (int i = k / 2 - 1; i >= 0; i--)
            adjMinHeap(nums, i, k);
    }
    int findKthLargest(vector<int>& nums, int k) {
        buildMinHeap(nums, k);
        for (int i = k; i < nums.size(); i++) {
            if (nums[i] < nums[0])
                continue;
            swap(nums[0], nums[i]);
            adjMinHeap(nums, 0, k);
        }
        return nums[0];
    }
};

堆(优先队列)的构建与调整复杂度分析

建堆的复杂度

  • 时间复杂度:O(k),其中 k 是堆的大小。
  • 原因:建堆的过程是从数组的中间位置开始,然后逐步向上调整,直到根节点。由于堆的大小是 k,因此建堆的时间复杂度是 O(k)。

调整堆的复杂度

  • 时间复杂度:O(n log k),其中 n 是数组的大小,k 是堆的大小。
  • 原因:每次调整堆的时间复杂度是 O(log k),因为堆的调整是通过递归进行的,每次递归都会将问题规模减少一半。由于需要对数组中的每个元素进行一次调整,因此总的时间复杂度是 O(n log k)。

总的时间复杂度

  • 总时间复杂度:O(k + n log k)
  • 为什么是 O(n):尽管建堆和调整堆的时间复杂度分别是 O(k) 和 O(n log k),但由于 k 的数量级通常远小于 n,因此总的时间复杂度近似为 O(n log k)。在实际应用中,通常可以忽略 k 相对于 n 的影响,从而认为总的时间复杂度是 O(n)。

空间复杂度

  • O(k),因为只需要存储 k 个元素的最大堆。

方法二:快排思想

在这里插入图片描述

题目分析

快速选择算法是一种用于在未排序的数组中找到第 k 大的元素的算法。它基于快速排序的思想,但与快速排序不同,快速选择算法只对部分数组进行排序,从而找到第 k 大的元素。

算法步骤

  1. 选择数组的第一个元素作为划分元素。
  2. 将数组分为两部分:小于划分元素的和大于划分元素的。
  3. 如果 k 小于等于划分元素左侧的元素数量,则在左侧递归查找第 k 大的元素。
  4. 如果 k 大于划分元素左侧的元素数量,则在右侧递归查找第 k 大的元素。
  5. 重复步骤2-4,直到找到第 k 大的元素。

算法流程

开始 选择数组的第一个元素作为划分元素 将数组分为两部分 小于划分元素和大于划分元素 如果k小于等于划分元素左侧的元素数量 在左侧递归查找第k大的元素 否则在右侧递归查找第k大的元素 在右侧递归查找第k大的元素 返回第k大的元素 结束

具体代码

class Solution {
public:
    // 快速选择函数,用于找到数组中第k大的元素
    int quickselect(vector<int> &nums, int l, int r, int k) {
        // 如果左指针等于右指针,说明数组只有一个元素,直接返回该元素
        if (l == r)
            return nums[k];
        // 选择左边界作为划分元素
        int partition = nums[l], i = l - 1, j = r + 1;
        // 双指针遍历,将小于划分元素的放在左边,大于的放在右边
        while (i < j) {
            // 找到第一个大于等于划分元素的索引
            do i++; while (nums[i] < partition);
            // 找到第一个小于等于划分元素的索引
            do j--; while (nums[j] > partition);
            // 如果两个指针还没有相遇,交换它们指向的元素
            if (i < j)
                swap(nums[i], nums[j]);
        }
        // 如果k小于等于右指针,说明第k大的元素在左边,递归左半部分
        if (k <= j)return quickselect(nums, l, j, k);
        // 否则在右边,递归右半部分
        else return quickselect(nums, j + 1, r, k);
    }

    // 主函数,用于找到数组中第k大的元素
    int findKthLargest(vector<int> &nums, int k) {
        // 获取数组长度
        int n = nums.size();
        // 调用快速选择函数,注意第k大的元素实际上是第n-k小的元素
        return quickselect(nums, 0, n - 1, n - k);
    }
};

算法分析

  • 时间复杂度: O(n),最坏情况下时间复杂度为 O(n^2),但平均情况下接近 O(n)。
  • 空间复杂度: O(log n),由于使用了递归,递归深度为 log n。
  • 应用场景: 适用于查找第 k 大的元素,尤其当 k 远小于 n 时,效率较高。

相似题目

题目链接
215. 数组中的第K个最大元素https://leetcode.cn/problems/kth-largest-element-in-an-array/
218. 天际线问题https://leetcode.cn/problems/the-skyline-problem/
234. 回文链表https://leetcode.cn/problems/palindrome-linked-list/

标签:nums,int,复杂度,元素,快排,算法,数组,小根堆,leetcode215
From: https://blog.csdn.net/qq_51350957/article/details/141490892

相关文章

  • 大根堆和小根堆的介绍
    堆(Heap)的基本概念堆是一种完全二叉树(CompleteBinaryTree),其性质使得堆可以高效地支持以下操作:插入(Insert):将一个新元素加入到堆中。删除最大/最小元素(DeleteMax/Min):移除并返回堆中的最大(大根堆)或最小(小根堆)元素。获取最大/最小元素(GetMax/Min):返回堆中的最大(大根堆)或最小(小......
  • 【数据结构】大根堆和小根堆
    大根堆实现逻辑从整棵树的最后一颗子树开始调整,每次都让根节点和左右孩子去比较,如果根节点比左右孩子的最大值要小,那么就将这两个值进行交换,然后此时这颗子树变成了大根堆,再看下一颗树然后对下一颗树进行相同的处理方法,后面的子树依次交换:当每棵子树都是大根堆的情况......
  • 快排CMS1.2文件上传漏洞
    侵权声明本文章中的所有内容(包括但不限于文字、图像和其他媒体)仅供教育和参考目的。如果在本文章中使用了任何受版权保护的材料,我们满怀敬意地承认该内容的版权归原作者所有。如果您是版权持有人,并且认为您的作品被侵犯,请通过以下方式与我们联系:[[email protected]]。我们将在确......
  • Java 经典排序算法代码 + 注释分析(冒泡、选择、插入、希尔、快排、计数、堆排、归并)
    Java经典排序算法代码+注释分析(冒泡、选择、插入、希尔、快排、计数、堆排、归并)以下是八种经典排序算法的代码,Java8亲测可用,可以直接运行importjava.util.Arrays;publicclassSort{privatestaticfinalint[]nums={3,44,38,5,47,15,36,26,27......
  • 最小数字游戏(Lc2974)——模拟+优先队列(小根堆)、排序+交换
    你有一个下标从 0 开始、长度为 偶数 的整数数组 nums ,同时还有一个空数组 arr 。Alice和Bob决定玩一个游戏,游戏中每一轮Alice和Bob都会各自执行一次操作。游戏规则如下:每一轮,Alice先从 nums 中移除一个 最小 元素,然后Bob执行同样的操作。接着,Bob会将......
  • 比快排还快(参与者:陈卓)
    有这样一种排序问题:任意给定k个(k<10w)不重复的数字,每个数字的取值范围为[1,10w]。希望设计出一种排序算法对这10万个数字进行排序,时间复杂度尽可能小。第一时间我们可能会想使用快速排序,因为快排的时间复杂度只有O(nlogn)。但有没有比O(nlogn)更快的排序方法呢?你可能会有疑问:O(nl......
  • 二分#背包#快排#LCS详解
    二分#背包#快排#LCS详解文章目录二分#背包#快排#LCS详解1.二分搜索2.01背包问题3.快速排序4.最长公共子序列1.二分搜索在处理大规模数据集时,查找操作的效率显得尤为重要。二分搜索是一种在有序数组中查找目标值的高效算法,其时间复杂度为O(logn)。二分搜索通......
  • 优雅的快排之分治与递归思想,透彻理解快排
    摘要:给你一个数组,要求你对其按照从小到大的顺序进行排序.你是怎么做的呢?英国计算机科学家东尼.霍尔在英国国家物理实验室工作的时候提出一种名为快速排序的排序算法,它可以高效地将一个数组进行快速排序.那么霍尔是怎么想到的?接下来根据从y总那里学到的以及个人的理解来介......
  • 每天写两道(四)最大子数组和、手撕快排
    53.最大子数组和.-力扣(LeetCode)给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。示例1:输入:nums=[-2,1,-3,4,-1,2,1,-5,4]输出:6解释:连续子数组 [4,-1,2,1]的和最大,为 6。......
  • Python之快排算法
    快排算法的思路:从list中取出下标为0的值定义三个list进行循环,大于list[0]放入一个A,小于的放入B,其他的放入C拼接:A+C+B代码实现:list=[13,8,11,17,5,6,1,1,1]defQuickSort(list):iflen(list)<=1:#判断如果小于等于1,则无需排序,直接返回即可......