从小白开始刷算法 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和最小堆
相关文章
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
11天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
63 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
23 2
|
1月前
|
算法 搜索推荐
数据结构与算法学习十八:堆排序
这篇文章介绍了堆排序是一种通过构建堆数据结构来实现的高效排序算法,具有平均和最坏时间复杂度为O(nlogn)的特点。
70 0
数据结构与算法学习十八:堆排序
|
1月前
|
算法 搜索推荐
算法之堆排序
本文介绍了堆排序算法的原理和实现,通过构建最大堆或最小堆,利用堆的性质进行高效的排序,并提供了具体的编程实现细节和示例。
21 0
算法之堆排序
|
1月前
|
算法 Java Go
深入了解堆排序算法
深入了解堆排序算法
22 1
|
3月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
51 6
|
3月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
50 1
|
3月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
60 1