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

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

692.前K个高频单词


给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。


返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。


示例 1:


输入: words = [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2

输出: [“i”, “love”]

解析: “i” 和 “love” 为出现次数最多的两个单词,均为2次。

注意,按字母顺序 “i” 在 “love” 之前。

示例 2:


输入: [“the”, “day”, “is”, “sunny”, “the”, “the”, “the”, “sunny”, “is”, “is”], k = 4

输出: [“the”, “is”, “sunny”, “day”]

解析: “the”, “is”, “sunny” 和 “day” 是出现次数最多的四个单词,

出现次数依次为 4, 3, 2 和 1 次。


题目来源:力扣(LeetCode)


最下堆排序+哈希表思路


能否写出:不能写出。

时间:1个多小时

思路:

第一次学习最小堆堆排序,前一篇是最大堆排序

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


堆排序介绍


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


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

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

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


堆排序的具体步骤如下:


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

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

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

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


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


最小堆也是使用数组来实现的,与最大堆类似。不同之处在于,最小堆中父节点的值小于等于其子节点的值,而最大堆中父节点的值大于等于其子节点的值。最小堆的构建和调整过程与最大堆类似,只是在比较和交换的时候使用相反的逻辑。


最小堆是使用树结构?


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


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


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

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

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

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


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


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

image.jpeg

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

  • 所有节点位置计算: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 MinHeap {
    public static void heapSort(int[] nums) {
    int n = nums.length;
    // 构建最小堆
    buildMinHeap(nums);
    // 进行堆排序
    for (int i = n - 1; i >= 0; i--) {
        // 将堆顶元素(最小值)与当前末尾元素交换
        swap(nums, 0, i);
        // 调整堆,保持最小堆性质
        minHeapify(nums, i, 0);
      }
  }
    public static void buildMinHeap(int[] nums) {
        int n = nums.length;
        for (int i = n / 2 - 1; i >= 0; i--) {
            minHeapify(nums, n, i);
        }
    }
    public static void minHeapify(int[] nums, int heapSize, int index) {
        int smallest = index;
        int left = 2 * index + 1;
        int right = 2 * index + 2;
        if (left < heapSize && nums[left] < nums[smallest]) {
            smallest = left;
        }
        if (right < heapSize && nums[right] < nums[smallest]) {
            smallest = right;
        }
        if (smallest != index) {
            swap(nums, index, smallest);
            minHeapify(nums, heapSize, smallest);
        }
    }
    public static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

思路:


首先,使用HashMap统计每个单词的出现频率,其中单词作为键,频率作为值。


然后,我们使用最小堆(PriorityQueue)来保存频率前k高的单词。在最小堆中,我们定义了一个自定义的比较器Lambda表达式来进行排序。比较器的逻辑如下:


首先,我们比较两个单词的频率是否相等。

如果频率相等,则按照字典序降序排列,即b.compareTo(a)

如果频率不相等,则按照频率升序排列,即frequencyMap.get(a) -frequencyMap.get(b)


接下来,我们遍历频率Map,将单词依次加入最小堆中。如果堆的大小超过了k,则移除堆顶的元素,保持堆的大小为k。


在调用minHeap.poll()方法时,会调用比较器(Comparator)来进行元素的比较。在这段代码中,我们使用的比较器是通过Lambda表达式定义的,它会根据单词的频率和字典序进行比较。

最后,我们将堆中的单词依次弹出,并将其添加到结果列表中。由于最小堆中的单词是按照频率升序排列的,因此我们需要将结果列表进行反转,使其按照频率降序排列。

最终,返回结果列表即为出现频率前k高的单词。


// 仅是我的思路代码,leetcode上大神更厉害
class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        // 统计每个单词的出现频率
        Map<String, Integer> frequencyMap = new HashMap<>();
        for (String word : words) {
            frequencyMap.put(word, frequencyMap.getOrDefault(word, 0) + 1);
        }
        // 使用最小堆来保存频率前k高的单词
        PriorityQueue<String> minHeap = new PriorityQueue<>(
                (a, b) -> frequencyMap.get(a).equals(frequencyMap.get(b))
                        ? b.compareTo(a) : frequencyMap.get(a) - frequencyMap.get(b));
        // 遍历频率Map,维护最小堆的大小为k
        for (String word : frequencyMap.keySet()) {
            minHeap.offer(word);
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }
        // 构建结果列表
        List<String> result = new ArrayList<>();
        while (!minHeap.isEmpty()) {
            result.add(minHeap.poll());
        }
        Collections.reverse(result);
        return result;
    }
}

时间复杂度:O(nlogk)

  • n是单词的总数,k是要求的频率前k高的单词数。我们需要遍历整个单词数组来统计频率,同时最小堆的插入和弹出操作的时间复杂度为O(logk)

空间复杂度:O(n)

  • 用于存储频率Map和最小堆
相关文章
|
3月前
|
算法 Python
数据结构算法--4堆排序
堆排序过程概述:建立大根堆,将堆顶最大元素移出并替换为末尾元素,调整保持堆性质,重复此过程直至堆为空,实现排序。时间复杂度为O(nlogn)。Python中可用heapq模块进行堆操作。
|
1月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
36 6
|
1月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
37 1
|
1月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
35 1
|
22天前
|
数据采集 算法
基于PSO粒子群算法的三角形采集堆轨道优化matlab仿真
该程序利用PSO算法优化5个4*20矩阵中的模块采集轨迹,确保采集的物品数量及元素含量符合要求。在MATLAB2022a上运行,通过迭代寻优,选择最佳模块组合并优化轨道,使采集效率、路径长度及时间等综合指标最优。具体算法实现了粒子状态更新、需求量差值评估及轨迹优化等功能,最终输出最优轨迹及其相关性能指标。
|
1月前
|
存储 算法 Java
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
60 0
|
1月前
|
算法 Java
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
32 0
|
1月前
|
算法 Java
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
43 0
|
1月前
|
存储 算法 Java
LeetCode初级算法题:两数之和+斐波拉契数列多种java解法
LeetCode初级算法题:两数之和+斐波拉契数列多种java解法
19 0
|
1月前
|
算法 Java 索引
LeetCode初级算法题:寻找数组的中心索引+x的平方根+三个数的最大乘积+Leetcode 149:直线上最多的点数 Java详解
LeetCode初级算法题:寻找数组的中心索引+x的平方根+三个数的最大乘积+Leetcode 149:直线上最多的点数 Java详解
28 0