代码随想录【day 12 栈与队列】| 239. 滑动窗口最大值 347.前 K 个高频元素

文章介绍了如何使用双向队列解决LeetCode中的滑动窗口最大值问题,以及利用大顶堆和小顶堆找到数组中前K个高频元素。解题策略包括维护单调队列以实时获取窗口内最大值,以及使用哈希表和优先队列来高效计算频率并选取高频元素。
摘要由CSDN通过智能技术生成


LeetCode 239. 滑动窗口最大值

题目链接:239.滑动窗口最大值
卡哥文解
视频讲解
  • 题目描述: 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
  • 返回 滑动窗口中的最大值 。
解题思路(双向队列)

1、自定义一个队列 放进窗口里的元素,随着窗口的移动,队列也一进一出,每次移动之后队列给出里面的最大值。
2、声明了一个变量 deque< int>que ,用于存储下标。
这个变量有以下 特点:

  • 变量的最前端(即 que.front())是此次遍历的最大值的下标
  • 当遇到新的数时,将新的数和双向队列的末尾(也就是que.back())比较,如果末尾比新数小,则把末尾扔掉,直到该队列的末尾比新数大或者队列为空的时候才停止,做法有点像使用栈进行括号匹配。
  • 双项队列中的所有值都要在窗口范围内

3、模拟过程

初始状态:L=R=0,队列:{}
i=0,nums[0]=1。队列为空,直接加入。队列:{1}
i=1,nums[1]=3。队尾值为1,3>1,弹出队尾值,加入3。队列:{3}
i=2,nums[2]=-1。队尾值为3,-1<3,直接加入。队列:{3,-1}。此时窗口已经形成,L=0,R=2,result=[3]
i=3,nums[3]=-3。队尾值为-1,-3<-1,直接加入。队列:{3,-1,-3}。队首3对应的下标为1,L=1,R=3,有效。result=[3,3]
i=4,nums[4]=5。队尾值为-3,5>-3,依次弹出后加入。队列:{5}。此时L=2,R=4,有效。result=[3,3,5]
i=5,nums[5]=3。队尾值为5,3<5,直接加入。队列:{5,3}。此时L=3,R=5,有效。result=[3,3,5,5]
i=6,nums[6]=6。队尾值为3,6>3,依次弹出后加入。队列:{6}。此时L=4,R=6,有效。result=[3,3,5,5,6]
i=7,nums[7]=7。队尾值为6,7>6,弹出队尾值后加入。队列:{7}。此时L=5,R=7,有效。result=[3,3,5,5,6,7]

实现难点

1、pop操作判断:que不为空 并且 当前元素是否与队列出口元素的数值相等
2、push操作判断:que不为空 并且 当前元素比队列出口元素小
3、此时单调队列的前端元素就是最大值

补充
  • 单调队列可以根据不同场景定义不同写法,保证队列里单调递减或递增的原则。
  • deque是 stack和queue默认的底层实现容器,可以两边扩展,deque里的元素并不是严格连续分布。
代码实现
class Solution {
public:
    // 定义单调队列
    class MyQueue{ // 单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 弹出元素时,比较当前要弹出的数值是否等于队列出口元素的数值 相等则弹出
        // pop之前判断队列当前是否为空
        void pop(int value){
            if(!que.empty() && value == que.front()){
                que.pop_front();
            }
        }
        // push的数值大于入口元素的数值 将队列后端的数值弹出 直到push的数值≤入口元素
        // 这样保持队列里的数值是单调从大到小
        // 在例子[1,3,-1,-3,5,3,6,7]中 一开始传入的value是1 比3(que.back())小 故不push 1 
        void push(int value){
            while(!que.empty() && value > que.back()){
                que.pop_back();
            }
            que.push_back(value);
        }
        // 查询当前队列里的最大值 直接返回队列前端front
        int front(){
            return que.front();
        }

    };
    
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for(int i=0;i<k;i++){ // 先将前k元素放进队列
            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;
    }
};

LeetCode 347.前 K 个高频元素

题目链接:LeetCode347.前K个高频元素
卡哥文解
视频讲解
  • 题目描述: 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
解题思路(大顶堆和小顶堆)
  • 求数组里每个元素的频率 (利用map 记录 key 以及对应的频率 value
  • 对频率进行排序 (若对所有元素排序 时间复杂度(快排):O(nlogn))使用一种容器适配器 优先队列
  • 找出前K个高频元素
  • 只维护 k个元素的堆 若使用大顶堆 若有新的元素push进来时 则要将根节点最大的元素pop
    故使用 小顶堆 每次新元素进来 将最小的元素pop出去
补充
  • 优先队列的底层实现就是堆:对外接口从队头取元素,从队尾添加元素
  • 优先队列内部元素自动按照元素的权值排列:缺省情况下 priority_queue利用max-heap(大顶堆)对元素排序 故自定义比较形式mycomprision
  • 大顶堆是以vector为表现形式的完全二叉树
  • 堆是一棵完全二叉树,树中每个节点的值都不小于(或不大于)其左右孩子的值
    大顶堆:父亲结点 ≥ 左右孩子
    小顶堆:父亲结点 ≤ 左右孩子
语法补充

1、C++ pair的基本用法总结
pair 将2个数据合成一组数据 STL中的map将key和value放在一起来保存
当一个函数需要返回2个数据时 选择pair
pair的实现是一个结构体 主要的两个成员变量是 first second

2、priority_queue<Type, Container, Functional>;
Type是要存放的数据类型
Container是实现底层堆的容器,必须是数组实现的容器,如vector、deque
Functional是比较方式/比较函数/优先级
priority_queue;
此时默认的容器是vector,默认的比较方式是大顶堆less

3、emplace_back()函数是 C++ 11 新增加的,其功能和 push_back() 相同,都是在 vector 容器的尾部添加一个元素。

代码实现(哈希unordered_map+优先队列priority_queue)
class Solution {
public:
    // 小顶堆
    class mycomparision{
        public:
            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; // 存储元素key以及对应的频率 value
        for(int i=0;i<nums.size();i++){
            map[nums[i]]++;
        }
        // 对频率排序
        // 定义一个小顶堆 大小为k
        // 创建优先队列
        priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparision> pri_que;
        // 固定大小为k的小顶堆 遍历map的元素
        for(auto& a:map){
            pri_que.push(a);
            if(pri_que.size()>k){
                pri_que.pop();
            }
        }
        // 记录结果
        vector<int>res(k);
        // 倒序输出
        for(int i=k-1;i>=0;i--){
            res[i]=(pri_que.top().first); // first对应的是元素
            pri_que.pop();
        }
        
        return res;

    }
};

栈和队列总结


1、 括号匹配问题

有三种不匹配的情况,

  • 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
  • 第二种情况,括号没有多余,但是 括号的类型没有匹配上。
  • 第三种情况,字符串里右方向的括号多余了,所以不匹配。

2、字符串去重问题
把字符串顺序放在一个栈中,如果相同 栈就弹出

3、逆波兰表达式问题
每个子表达式得出的结果压入栈 遇到运算符 分别讨论具体情况

队列
1、滑动窗口最大值 单调队列
2、求前 K 个高频元素 优先队列


day12总结复盘

1、学习了单调队列和优先队列
2、对于小顶堆的实现,C++语法不熟

参考链接
https://leetcode.cn/problems/top-k-frequent-elements/solutions/1283998/c-xiao-bai-you-hao-you-xian-dui-lie-de-j-53ay/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值