从小白开始刷算法 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)


其他思路


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

相关文章
|
22小时前
|
移动开发 算法 前端开发
前端算法之堆排序
前端算法之堆排序
14 1
|
22小时前
|
存储 算法
Leetcode 30天高效刷数据结构和算法 Day1 两数之和 —— 无序数组
给定一个无序整数数组和目标值,找出数组中和为目标值的两个数的下标。要求不重复且可按任意顺序返回。示例:输入nums = [2,7,11,15], target = 9,输出[0,1]。暴力解法时间复杂度O(n²),优化解法利用哈希表实现,时间复杂度O(n)。
20 0
|
23小时前
|
人工智能 搜索推荐 算法
sort-04-heap sort 堆排序算法详解
这是一个关于排序算法的系列文章摘要,包括了10篇关于不同排序算法的链接,如冒泡排序、快速排序、选择排序、堆排序等。堆排序是一种基于比较的排序算法,利用了近似完全二叉树的结构并满足最大堆或最小堆的性质。最大堆中,每个节点的值都大于或等于其子节点。文章详细解释了最大堆的概念、节点访问方式以及堆的操作,包括堆调整和堆排序的过程,并通过图解展示了堆排序的具体步骤。此外,还提供了一个Java实现的堆排序代码示例。整个排序系列来源于一个开源项目,读者可以通过链接查看完整内容。
sort-04-heap sort 堆排序算法详解
|
23小时前
|
算法
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
23 3
|
23小时前
|
存储 算法
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
22 1
|
23小时前
|
算法
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
18 3
|
23小时前
|
算法
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
34 1
|
23小时前
|
算法
代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结
代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结
24 1
|
23小时前
|
算法 API DataX
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
|
23小时前
|
算法 DataX
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”

热门文章

最新文章