力扣打卡Day13 栈与队列Part03 滑动窗口最大值+前K个高频元素

上强度了,感觉又学到了很多新知识,看题解和问文心一言,也学到了基础语法。

今天的内容主要是单调队列和优先级队列,最开始我以为两者是一样的,但是实际上不一样。在这里列一下两者的区别。

单调队列和优先级队列在数据结构和特性上存在明显的区别。以下是两者的主要区别:

  1. 定义与特性
    • 单调队列:单调队列维护了一个具有单调性的队列。按照遍历顺序入队、出队,每个元素仅入队、出队一次。队列中的元素始终保持单调增或单调减的特性。队首和队尾都可以进行出队操作,但只有队尾可以进行入队操作。对于递增/非降队列,队首元素为最小值;对于递减/非升队列,队首元素为最大值。
    • 优先级队列:优先级队列是一种允许操作的数据带有优先级的数据结构。出队列时,优先级高的元素先出队列。在Java中,PriorityQueue是优先级队列的一种实现,它基于堆数据结构,提供了返回最高优先级对象和添加新对象的基本操作。
  2. 实现方式
    • 单调队列:单调队列通过双向队列(deque)实现,以便支持从队列的两端进行出队操作。
    • 优先级队列:在Java中,PriorityQueue使用二叉堆(Heap)数据结构实现,确保队头元素(根节点)具有最高或最低优先级。
  3. 单调性
    • 单调队列:元素始终保持单调增或单调减的特性。
    • 优先级队列:元素本身不具有单调性,但根据优先级排序,优先级最高的元素始终位于队头。
  4. 元素关系
    • 单调队列:元素之间的关系具有单调性,即队首元素始终是最小值或最大值。
    • 优先级队列:元素之间的关系基于优先级,而非单调性。
  5. STL操作
    • 单调队列:在STL(Standard Template Library)中,单调队列通常使用deque(双向队列)来实现。
    • 优先级队列:在STL中,PriorityQueue提供了优先级队列的实现。
  6. 长度与容量
    • 单调队列:长度取决于输入数据的合法性。
    • 优先级队列:长度始终与输入数据的数量等同,没有容量限制,可以插入任意多个元素。
  7. 时间复杂度
    • 单调队列:在每次加入或删除元素时都保持序列里的元素有序,但具体的时间复杂度取决于实现方式和操作。
    • 优先级队列:PriorityQueue中插入和删除元素的时间复杂度通常为O(logN),其中N为队列中元素的数量。

总结来说,单调队列和优先级队列在数据结构、实现方式、元素关系、STL操作、长度与容量以及时间复杂度等方面均存在显著差异。单调队列主要关注元素之间的单调性,而优先级队列则关注元素的优先级。

priority_queue 是 C++ 标准库中的一个容器适配器,它封装了另一个容器(通常是 vectordeque 或其他支持随机访问迭代器的容器),并提供了一个队列接口,但其中的元素不是按照它们被添加的顺序出队的,而是根据它们的优先级。priority_queue 的默认行为是一个最大堆,这意味着具有最高优先级的元素(即最大的元素)总是位于堆顶,并首先被出队(通过调用 top() 和 pop())。

priority_queue 有三个模板参数:

  1. 第一个模板参数是存储在队列中的数据类型。
  2. 第二个模板参数是底层容器类型,它必须支持随机访问迭代器(默认为 std::vector)。
  3. 第三个模板参数是比较函数对象,它决定了元素的排序方式(默认为 std::less<T>,其中 T 是存储在队列中的数据类型)(与快排相反)。

在你提供的代码中,priority_queue 被用作一个最大堆来存储整数对(由数字和其出现次数组成),并且使用了自定义的比较类 mycomparison 来反转默认的排序顺序,使得出现次数最少的元素在堆顶。但是,实际上由于 mycomparison 比较的是 second 成员(即出现次数),因此这个优先队列实际上存储的是出现次数最多的元素在堆顶,即它是一个最大堆。

下面是一个简化的 priority_queue 使用示例,演示了如何使用默认的最大堆行为:

#include <iostream>  
#include <queue>  
#include <vector>  
  
int main() {  
    std::priority_queue<int> pq; // 默认最大堆  
  
    pq.push(3);  
    pq.push(1);  
    pq.push(4);  
    pq.push(1);  
    pq.push(5);  
  
    while (!pq.empty()) {  
        std::cout << pq.top() << " "; // 输出:5 4 3 1 1(可能因实现而异)  
        pq.pop();  
    }  
  
    return 0;  
}

在这个例子中,priority_queue 被用作一个最大堆,并且每次调用 top() 都会返回当前堆顶的最大元素。每次调用 pop() 都会移除堆顶元素,并重新调整堆以保持其属性。

在下面两道题中就分别用到了单调队列和优先级队列,让我一把子加深了了解。 

239. 滑动窗口最大值 - 力扣(LeetCode)

注释都写好了,也能不看题解单独写出来了,应该是基本理解了。

这题主要是考察了单调队列的实现,之前做力扣的时候基本上很少做这种需要自己定义类的题,需要多练习。

class Solution {
private:
    // 使用双向队列deque来实现MyQueue
    class MyQueue {
    public:
        // 定义一个双向队列que
        // 有pop_back()、pop_front()、push_back()、push_front()函数
        // 双向都能插入和弹出
        // 但是单向一般是队尾进队首出
        deque<int> que;
        // 返回队首元素
        int front() {
            return que.front();
        }
        // 单调队列的关键,当准备插入的时候,先判断一下要插入的值和队列的末尾的值的大小
        void push(int val) {
            while(!que.empty()&&val>que.back()) {
                que.pop_back();
            }
            // 然后再插入
            que.push_back(val);
        }
        // 当队列不为空,然后要弹出的值和队首的一样就可以弹出了
        void pop(int val) {
            if(!que.empty()&&val==que.front()) {
                que.pop_front();
            }
        }
    }; // 别忘记逗号
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int>result;
        // 先把前k个元素放进去,防止待会的遍历nums[i-k]不在范围内
        for(int i=0;i<k;i++) {
            que.push(nums[i]);
        }
        result.push_back(que.front());

        for(int i=k;i<nums.size();i++) {
            que.pop(nums[i-k]);
            que.push(nums[i]);
            result.push_back(que.front());
        }
        return result;
    }
};

347. 前 K 个高频元素 - 力扣(LeetCode)

这题主要是考察了优先级队列,这个在STL里已经有对应的PriorityDeque了,接着就是学习如何使用。这里最开始是定义了一个mycomparison,来帮助定义小顶堆,不然默认是大顶堆。

左侧大于右侧->小顶堆

右侧大于左侧->大顶堆

class Solution {
public:
    class mycomparison{
    public:
        // operator()定义函数对象
        // const可以防止改变值
        // &可以直接访问对象,而不是以拷贝的形式,提高效率
        bool operator()(const pair<int,int>& lhs,const pair<int,int>& rhs) {
            return lhs.second>rhs.second; //小顶堆
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int>map; // 定义一个map来存储元素与元素出现的次数
        vector<int>result(k); // 定义一个result存储答案,大小固定为k
        for(int i=0;i<nums.size();i++) {
            map[nums[i]]++;
        }
        // 定义优先队列
        // 第一项是元素类型,第二项是原始的容器,第三项是排列规则
        priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> que;

        // 按照刚刚定义的小顶堆que,来放对应的pair
        // unordered_map<int,int>::iterator it 和 auto it是一样的
        for(auto it=map.begin();it!=map.end();it++) {
            que.push(*it);
            if(que.size()>k) que.pop(); // que的size超出了题目要求的k就弹出
        }
        // 此时小顶堆里栈顶存的是偏小的数,所以要倒着放进答案里
        for(int i=k-1;i>=0;i--) {
            result[i]=que.top().first;
            que.pop();
        }
        return result;
    }
};

这里要补充的几个个人不熟悉的点:

  1. operator()定义函数对象:
  2. const &的使用:
  3. priority_queue<>内的参数
  4. map里的元素类型是pair<,>
  5. ::iterator it与auto it

大部分都在注释里写好了,不赘述了

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值