从小白开始刷算法 Heap 堆篇 最大堆排序 leetcode.215

简介: 从小白开始刷算法 Heap 堆篇 最大堆排序 leetcode.215

215.数组中的第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


题目来源:力扣(LeetCode)


堆排序


能否写出:不能写出。

时间:1个多小时

思路:这题其实是一题排序算法,我这边先使用堆排序

第一次学习最大堆堆排序,详细介绍一下。


堆排序介绍


堆排序是一种基于二叉堆(Binary Heap)数据结构的排序算法,它利用堆的性质进行排序。堆是一种特殊的二叉树,它满足以下两个性质:


最大堆性质:对于堆中任意节点 i,其父节点的值大于等于它的值,即 heap[parent(i)] >= heap[i]。

完全二叉树性质:堆是一棵完全二叉树,除了最底层外,其他层都是满的,且最底层从左到右填充。

堆排序的基本思想是先构建一个最大堆,然后将堆顶元素(最大值)与堆中的末尾元素交换,然后将剩余元素重新调整为最大堆,重复这个过程直到所有元素都有序。


堆排序的具体步骤如下:


构建最大堆:从数组的中间位置开始,从右到左对每个非叶子节点进行调整,使其满足最大堆性质。这一步将整个数组调整为一个最大堆。

堆排序:从数组的末尾开始,依次将堆顶元素与当前末尾元素交换,然后调整剩余元素为最大堆。每次交换后,将堆的大小减1,排除已排序的元素。重复这个过程,直到堆中只剩下一个元素。

排序完成:经过堆排序后,数组中的元素按照从小到大的顺序排列。

堆排序的时间复杂度为 O(nlogn),其中 n 是数组的长度。它是一种原地排序算法,不需要额外的空间,但由于涉及到交换操作,所以是一种不稳定的排序算法。


堆排序在实际应用中具有一定的局限性,因为它对数据的随机访问较多,对于较大规模的数据或者外部存储的数据,堆排序可能会导致较差的缓存性能。但在某些特定场景下,堆排序的性能相对较好,例如需要找出前 k 个最大或最小的元素时,堆排序可以通过部分排序来实现高效的解决方案。


最大堆是使用树结构?


最大堆是一种基于数组实现的完全二叉树,而不是树结构。尽管最大堆的表示形式在视觉上看起来像是一棵树,但实际上它是通过数组来实现的。


最大堆的性质是父节点的值大于等于其子节点的值。通过使用数组来表示最大堆,可以方便地满足这一性质,并且具有一些优势:


数组是一种连续存储的数据结构,相比树结构,它具有更好的缓存性能。在访问数组元素时,可以利用 CPU 缓存的特性,提高访问效率。

数组可以通过索引直接访问元素,而不需要像树结构一样需要遍历搜索。这使得对最大堆的插入、删除等操作更加高效。

数组实现的最大堆相对于树结构来说,占用的内存空间更小。树结构需要额外的指针和连接操作,而数组只需要连续的内存空间即可。

综上所述,使用数组来实现最大堆既能满足最大堆的性质,又能提供更好的性能和节省内存空间。因此,最大堆通常采用数组来表示,而不是树结构。


我们来一行行看代码,我最喜欢debug式看代码。


首先,记住这张图,关键点在于根节点(num[0]),节点位置(num[7]之前),和叶子节点位置(num[7]之后)。

1687247774503.jpg

节点位置与叶子节点位置,他们之间是可以通过计算得出来的

  • 所有节点位置计算:nums.length/2-1 例:13/2-1 = 5; num[5]之前的节点位置
  • 节点位置与叶子节点位置计算:拿num[1]和num[5]举例
  • 左节点计算:int left =2 * index + 1 例:num[1]左节点 2 * 1+1= 3 num[5]子节点 2 * 5+1= 11

  右节点计算:int right =2 * index + 2 例:num[1]右节点 2 * 1+2= 4 num[5]子节点 2 * 5+2= 12


// 仅是我的思路代码,leetcode上大神更厉害
class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k <= 0 || k > nums.length) {
            return -1;  // 无效输入,返回一个无效值
        }
        // 构建最大堆
        buildMaxHeap(nums);
        // 进行堆排序
        //从nums数组的最后一个元素开始,递减地遍历到第(nums.length - k + 1)个元素。其中nums.length是数组的长度,k是要找的第K个最大元素的位置。
        for (int i = nums.length - 1; i >= nums.length - k + 1; i--) {
            //下沉堆顶最大元素,并断开树的链接
            swap(nums, 0, i);  // 将堆顶元素与当前末尾元素交换
            maxHeapify(nums, i, 0);  // 调整堆,保持最大堆性质
        }
        // 返回第 k 个最大元素
        return nums[0];
    }
    // 构建最大堆
    private void buildMaxHeap(int[] nums) {
        int n = nums.length;
        for (int i = n / 2 - 1; i >= 0; i--) {
            maxHeapify(nums, n, i);
        }
    }
    // 调整堆,保持最大堆性质
    private void maxHeapify(int[] nums, int heapSize, int index) {
        int largest = index;  // 假设当前节点最大
        int left = 2 * index + 1; // 获取左节点
        int right = 2 * index + 2;// 获取右节点
        // 如果左子节点存在且大于 当前节点 的值
        // 则更新 largest 为左子节点的索引
        if (left < heapSize && nums[left] > nums[largest]) {
            largest = left;
        }
        // 如果右子节点存在且大于 当前节点 和 左子节点 的值
        // 则更新 largest 为右子节点的索引
        if (right < heapSize && nums[right] > nums[largest]) {
            largest = right;
        }
        //如果 largest 不等于当前节点的索引,说明当前节点不是最大的,
        //则将当前节点与 largest 所指的节点进行交换,以保证最大值上浮到当前节点位置。
        if (largest != index) {
            swap(nums, index, largest);  // 将当前节点与最大节点交换
            //递归调用 maxHeapify,以便在交换后继续对 largest 所指的子节点进行堆化操作,以保持最大堆的性质。
            maxHeapify(nums, heapSize, largest);  // 递归调整子堆
        }
    }
    // 交换数组中两个元素的位置
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

第K个最大元素

时间复杂度:

  • 构建最小堆的时间复杂度为O(K)。
  • 遍历数组并插入元素的时间复杂度为O((N-K)logK),其中N为数组长度。
  • 总时间复杂度为O(K + (N-K)logK),近似于O(NlogK)

空间复杂度:

  • 最小堆的空间复杂度为O(K)


其他思路


快速排序 下次有机会写一篇文章

相关文章
|
19天前
|
移动开发 算法 前端开发
前端算法之堆排序
前端算法之堆排序
16 1
|
10天前
|
算法 C++
c++算法学习笔记 (19) 堆
c++算法学习笔记 (19) 堆
|
11天前
|
算法
【优选算法】——Leetcode——LCR 179. 查找总价格为目标值的两个商品
【优选算法】——Leetcode——LCR 179. 查找总价格为目标值的两个商品
|
11天前
|
算法
【优选算法】——Leetcode——611. 有效三角形的个数
【优选算法】——Leetcode——611. 有效三角形的个数
|
11天前
|
算法 容器
【优选算法】—Leetcode—11—— 盛最多水的容器
【优选算法】—Leetcode—11—— 盛最多水的容器
|
11天前
|
算法
【优选算法】——Leetcode——202—— 快乐数
【优选算法】——Leetcode——202—— 快乐数
【优选算法】——Leetcode——202—— 快乐数
|
11天前
|
算法
[优选算法]——双指针——Leetcode——1089. 复写零
[优选算法]——双指针——Leetcode——1089. 复写零
|
11天前
|
算法
【优选算法】——双指针——Leetcode——283.移动零
【优选算法】——双指针——Leetcode——283.移动零
|
14天前
|
存储 机器学习/深度学习 算法
数据结构与算法⑬(第四章_中_续二)堆解决Topk问题+堆的概念选择题
数据结构与算法⑬(第四章_中_续二)堆解决Topk问题+堆的概念选择题
26 3
|
14天前
|
算法 搜索推荐
数据结构与算法⑫(第四章_中_续一)堆排序(详解)
数据结构与算法⑫(第四章_中_续一)堆排序(详解)
24 0