Offer必备算法22_优先级队列_堆_四道力扣题详解(由易到难)

news/2024/5/20 7:57:22 标签: 算法, leetcode, c++, 数据结构, 优先级队列,

目录

①力扣1046. 最后一块石头的重量

解析代码

②力扣703. 数据流中的第 K 大元素

解析代码

③力扣692. 前K个高频单词

解析代码

④力扣295. 数据流的中位数

解析代码

本篇完。


①力扣1046. 最后一块石头的重量

1046. 最后一块石头的重量

难度 简单

有一石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0

示例:

输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。

提示:

  • 1 <= stones.length <= 30
  • 1 <= stones[i] <= 1000

解析代码

        此题其实就是一个模拟的过程: 每次从石中拿出最大的元素以及次大的元素,然后将它们粉碎;如果还有剩余,就将剩余的石头继续放在原始的石里面。

        重复上面的操作,直到石里面只剩下一元素,或者没有元素(因为所有的石头可能全部抵消了)。那么主要的问题就是解决:

  • 如何顺利的拿出最大的石头以及次大的石头。
  • 并且将粉碎后的石头放入石中之后,也能快速找到下一轮粉碎的最大石头和次大石头。

        此时正好可以利用的特性来实现:创建一个大根,然后将所有的石头放入大根中,每次拿出前两个顶元素粉碎一下,如果还有剩余,就将剩余的石头继续放入中,这样就能快速地模拟出这个过程。

class Solution {
public:
    int lastStoneWeight(vector<int>& stones) {
        priority_queue<int> heap(stones.begin(), stones.end());
        while(heap.size() > 1)
        {
            int x1 = heap.top();
            heap.pop();
            int x2 = heap.top();
            heap.pop();
            heap.push(x1 - x2);
        }
        return heap.empty() ? 0 : heap.top();
    }
};


②力扣703. 数据流中的第 K 大元素

703. 数据流中的第 K 大元素

难度 简单

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:

输入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]

解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3);   // return 4
kthLargest.add(5);   // return 5
kthLargest.add(10);  // return 5
kthLargest.add(9);   // return 8
kthLargest.add(4);   // return 8

提示:

  • 1 <= k <= 10^4
  • 0 <= nums.length <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • -10^4 <= val <= 10^4
  • 最多调用 add 方法 10^4 次
  • 题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素
class KthLargest {
public:
    KthLargest(int k, vector<int>& nums) {

    }
    
    int add(int val) {

    }
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */

解析代码

一道TopK 问题:

数据结构算法⑬(第四章_中_续二)解决Topk问题+的概念选择题_16,23,53 ,31,94,72为什么是-CSDN博客

class KthLargest
{
    // 创建⼀个⼤⼩为 k 的⼩跟
    priority_queue<int, vector<int>, greater<int>> heap;
    int _k;
    
public:
    KthLargest(int k, vector<int>& nums)
        : _k(k)
    {
        for(auto& e : nums)
        {
            heap.push(e);
            if(heap.size() > _k)
            {
                heap.pop();
            }
        }
    }
    
    int add(int val)
    {
        heap.push(val);
        if(heap.size() > _k)
        {
            heap.pop();
        }
        return heap.top();
    }
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */


③力扣692. 前K个高频单词

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 次。

注意:

  • 1 <= words.length <= 500
  • 1 <= words[i] <= 10
  • words[i] 由小写英文字母组成。
  • k 的取值范围是 [1, 不同 words[i] 的数量]

进阶:尝试以 O(n log k) 时间复杂度和 O(n) 空间复杂度解决。

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {

    }
};

解析代码

一道Topk问题的拓展,有点考验语法能力:

稍微处理一下原数组:

  • 需要知道每一个单词出现的频次,因此可以先使用哈希表,统计出每一个单词出现的频次。
  • 然后在哈希表中,选出前 k 大的单词(为什么不在原数组中选?因为原数组中存在重复的单词,哈希表里面没有重复单词,并且还有每一个单词出现的频次)

如何使用,拿出前 k 大元素:

一、先定义一个自定义排序,我们需要的是前 k 大,因此需要一个小根。但是当两个字符串的频次相同的时候,我们需要的是字典序较小的,此时是一个大根的属性,在定义比较函数的时候需要注意。

  • 当两个字符串出现的频次不同的时候:需要的是基于频次比较的小根
  • 当两个字符串出现的频次相同的时候:需要的是基于字典序比较的大根

二、定义好比较器之后,依次将哈希表中的字符串插入到中,维持中的元素不超过 k 个。

三、遍历完整个哈希表后,中的剩余元素就是前 k 大的元素

class Solution {
public:
    struct cmp
    {
        bool operator()(const pair<string, int>& p1, const pair<string, int>& p2)
        {
            if(p1.second ==  p2.second) // 频次相同,字典序排序,小的在前,大根less
                return p1.first < p2.first;
            return p1.second > p2.second; // 频次大的在前->小根greater
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) {
        unordered_map<string, int> hash;
        for(auto& e : words) // 字符和频次映射到哈希表
        {
            hash[e]++;
        }
        priority_queue<pair<string, int>, vector<pair<string, int>>, cmp> heap;
        for(auto& e : hash) // Topk主逻辑
        {
            heap.push(e);
            if(heap.size() > k)
                heap.pop();
        }
        vector<string> ret(k); // 提取结果返回
        for(int i = k - 1; i >= 0; --i)
        {
            ret[i] = heap.top().first;
            heap.pop();
        }
        return ret;
    }
};


④力扣295. 数据流的中位数

295. 数据流的中位数

难度 困难

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 arr = [2,3,4] 的中位数是 3 。
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。

实现 MedianFinder 类:

  • MedianFinder() 初始化 MedianFinder 对象。

  • void addNum(int num) 将数据流中的整数 num 添加到数据结构中。

  • double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。

示例 1:

输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]

解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1);    // arr = [1]
medianFinder.addNum(2);    // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3);    // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0

提示:

  • -10^5 <= num <= 10^5
  • 在调用 findMedian 之前,数据结构中至少有一个元素
  • 最多 5 * 10^4 次调用 addNum 和 findMedian
class MedianFinder {
public:
    MedianFinder() {

    }
    
    void addNum(int num) {

    }
    
    double findMedian() {

    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

解析代码

此题有三个解法,三个解法的find函数的时间复杂度都是O(1)。看看add的时间复杂度:

  • 第一个解法是进来一个数,sort一下,这样add的时间复杂度是O(N*logN)。
  • 第二个解法是插入排序的思想,这样add的时间复杂度是O(N)。
  • 第三个解法是用两个来维护中位数,这样add的时间复杂度是O(logN)。

第三个解法难想,但是做完这道题以后能想起来就行。这里用第三个解法:

        将整个数组按照大小平分成两部分(如果不能平分,那就让较小部分的元素多⼀个), 较小的部分称为左侧部分,较大的部分称为右侧部分:

        将左侧部分放入大根中,然后将右侧元素放入小根中,这样就能在 O(1) 的时间内拿到中间的一个数或者两个数,进而求的平均数。

        于是问题就变成了如何将一个一个从数据流中过来的数据,动态调整到大根或者小根中,并且保证两个的元素一致,或者左侧的元素比右侧的元素多一个。

为了方便叙述,将左侧的大根记为 left ,右侧的小根记为 right ,数据流中来的数据记为 x 。

其实,就是分类讨论的过程:

  • 如果左右的数量相同, left.size() == right.size() :

如果两个都是空的,直接将数据 x 放入到 left 中; 如果两个非空:

  1. 如果元素要放入左侧,也就是 x <= left.top() :那就直接放,因为不会影响我们制定的规则;
  2. 如果要放入右侧可以先将 x 放入 right 中, 然后把 right 的顶元素放入 left 中
  • 如果左右的数量不相同。那就是 left.size()  =  right.size() + 1:

这个时候我们关心的是 x 是否会放入 left 中,导致 left 变得过多:

  1. 如果 x 放入 right 中,也就是 x >= right.top() ,直接放。
  2. 反之,就是需要放入 left 中: 可以先将 x 放入 left 中,然后把 left 的顶元素放入 right 中。

        只要每一个新来的元素按照上述规则执行,就能保证 left 中放着整个数组排序后的左半部分, right 中放着整个数组排序后的右半部分,就能在 O(1)的时间内求出平均数,且插入的时间复杂度首O(logN)。

class MedianFinder {
    // 较小的部分称为左侧部分,较大的部分称为右侧部分:
    // 将左侧部分放入大根中,然后将右侧元素放入小根中,
    priority_queue<int> left; // ⼤根
    priority_queue<int, vector<int>, greater<int>> right; // ⼩根

public:
    MedianFinder() {

    }
    
    void addNum(int num) {
        if(left.size() == right.size())
        {
            if(right.empty() || left.top() > num)
            {
                left.push(num);
            }
            else
            {
                right.push(num);
                left.push(right.top());
                right.pop();
            }
        }
        else
        {
            if(left.top() > num)
            {
                left.push(num);
                right.push(left.top());
                left.pop();
            }
            else
            {
                right.push(num);
            }
        }
    }
    double findMedian() {
        if(left.size() == right.size())
            return (left.top() + right.top()) / 2.0;
        return left.top();
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */


本篇完。

下一篇动态规划系列的是两个数组dp类型的OJ。

下下篇是BFS解决FloodFill算法


http://www.niftyadmin.cn/n/5473930.html

相关文章

SpringBoot整合Flowable/Activiti

SpringBoot版本: 2.0.1.RELEASE Flowable版本: 6.3.1 Activiti版本: 6.0.0 一.添加pom依赖 因为之前我整合的时候有报错关于sqlsession的错误,后面查询文章才发现flowable要排除掉mybatis,又没说具体排除哪一个,所以我这干脆全部排除了 <!-- Flowable dependencies -->…

智慧驿站式的“智慧公厕”,给城市新基建带来新变化

随着智慧城市建设的推进&#xff0c;智慧驿站作为一种多功能城市部件&#xff0c;正逐渐在城市中崭露头角。这些智慧驿站集合了智慧公厕的管理功能&#xff0c;为城市的新基建带来了全新的变革。本文以智慧驿站智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;大量精品案…

C语言交换两个变量值的方法,详细讲解

不管在学习哪门语言&#xff0c;都会遇到交换两个变量的这种问题&#xff0c;而且在面试测试题中也经常提到。既然出现的频率这么高&#xff0c;那我们今天就来讲讲交换两个变量常用的几种方法。 今天主要是基于C语言写的代码&#xff0c;不管哪种语言写的&#xff0c;应该核心…

CLoVe:在对比视觉语言模型中编码组合语言

CLoVe:在对比视觉语言模型中编码组合语言 摘要引言相关工作CLoVe: A Framework to Increase Compositionality in Contrastive VLMsSynthetic CaptionsHard NegativesModel Patching CLoVe: Encoding Compositional Language inContrastive Vision-Language Models 摘要 近年来…

Aurora8b10b(2)上板验证

文章目录 前言一、AXI_Stream数据产生模块二、上板效果总结 前言 上一篇内容我们已经详细介绍了基于aurora8b10b IP核的设计&#xff0c;本文将基于此进一步完善并且进行上板验证。 设计思路及代码思路参考FPGA奇哥系列网课 一、AXI_Stream数据产生模块 AXIS协议是非常简单的…

全面备份:自动化MySQL服务器上所有数据库的备份策略

这篇博客提供了一个批量备份MySQL数据库的Shell脚本&#xff0c;并包含了详细的注释和使用说明。这个脚本能够自动化地备份服务器上所有的数据库&#xff0c;排除系统数据库&#xff0c;并在备份完成后压缩和清理旧文件。 脚本内容及注释 下面是脚本的内容&#xff0c;其中包…

人工智能 - 服务于谁?

人工智能服务于谁&#xff1f; 人工智能服务于生存&#xff0c;其最终就是服务于战争&#xff08;热战、技术战、经济战&#xff09; 反正就是为了活着而战的决策。 既然人工智能所有结果&#xff0c;来自大数据的分挖掘&#xff08;分析&#xff09;也就是数据的应用&#x…

考研数学|《880题》这样刷效率最高,效果最好!

考研数学880题是很多考生在备考过程中会选择的一本习题集&#xff0c;它涵盖了大量的基础题、综合题和拓展题&#xff0c;对于巩固知识点和提升解题能力非常有帮助。针对你的情况&#xff0c;这里提供一些建议来提高刷题效率。 首先在过完1800基础篇后&#xff0c;你已经具备了…