代码随想录算法训练营第13天 | ● 239. 滑动窗口最大值 ● 347.前 K 个高频元素 ● 总结

day 13 第五章 栈与队列

今日内容:

● 239. 滑动窗口最大值

● 347.前 K 个高频元素

● 总结

详细布置


239. 滑动窗口最大值 (一刷至少需要理解思路)

【链接】(文章,视频,题目)

之前讲的都是栈的应用,这次该是队列的应用了。

本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。

题目链接/文章讲解/视频讲解:代码随想录

【第一想法与实现(困难)】

  • 暴力,两层for,显然时间复杂度是O(n * k) 。超出时间限制。

  • 提升效率的关键是,如何在滑动窗口移动一位的时候,使用前一个的大部分共同元素信息。可以维护一个单调队列(有序队列)来保存滑动窗口内的元素,这样求滑动窗口内最大值,就只是O(1)操作。但是滑动窗口(单调队列)的维护:

    • 出队,找到值为删除的元素,并删除(一半元素转圈平移)

    • 入队,找到值为插入的元素,并插入(一半元素转圈平移)

    • 看起来都是O(k/2)时间复杂度,整体还是O(n * k)

    • 感觉维护一个环形队列会更好?

【看后想法】

  • 需要每次获取滑动窗口中的最大值,不需要下标,考虑单调队列。由于每次新增和移除中已有一个元素,不关注整个窗口中的全部k个元素。为了降低新增删除的时间复杂度,只把有可能成为“窗口中最大值”的元素存储在单调队列中,而不是全部k个。有可能成为“窗口中最大值”的元素,满足两个条件:“足够大”是窗口中最大的,“足够新”,必须在滑动窗口中

    • 单调队列头部(出口)。肯定满足足够大,因为最大元素必然在单调队列头部。要满足足够新,则要关注这个元素何时移除出滑动窗口,需要同时执行队列头部移除操作。

    • 单调队列尾部(入口),是候选者的守门员。足够新。每次新进窗口的元素,也即需要新压入的元素,都在挑战原有尾部入口元素。新压入元素一定是新的:队尾入口(旧)元素在滑动窗口存在期间,新压入元素必然也存在。如果新压入元素比队尾入口元素还要大,那么则新元素“又大又新”,队尾入口元素“永无出头之日”,不可能成为滑动窗口最大值,因此可以直接把队尾元素移除。这是一个while条件,直至新压入元素<=队尾入口元素。新压入元素新而不大,原队尾入口元素大而不新,都有可能成为队列头

    • 上述操作,是为了保证队列的单调性,头大尾小

    • 上述定义的单调队列,可以称之为,找系列元素最值的专用数据结构,在这个系列元素本身新增删除的时候,仍然可以以O(1)的时间找到最值。本题中的系列元素就是滑动窗口的元素。其新增删除对应滑动窗口的移动

    • 整个理解过程,需要辅助代码随想录的动图和拆解每一步的静态图片。TODO

【实现困难】

  • 要在队列尾部移除元素,用双端队列deque

【自写代码】

class Solution {
public:
    class MyQueue {
        std::deque<int> queue_;
    public:
        MyQueue(){}
        // 弹出一个值,仅当在队列头才弹出
        void pop(int val) {
            if (!queue_.empty() && queue_.front() == val) {
                queue_.pop_front();
            }
        }
        // 压入一个值,与队列尾作比较
        void push(int val) {
            while (!queue_.empty() && val > queue_.back()) {
                queue_.pop_back();
            }
            queue_.push_back(val);
        }
        int front() {
            return queue_.front();
        }
    };
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        // 使用单调队列存储可能成为滑动窗口最大值的候选,双端队列deque实现
        vector<int> res;
        MyQueue que;
        for (int i = 0; i < k; i++) {
            que.push(nums[i]);
        }
        res.emplace_back(que.front());
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]);
            que.push(nums[i]);
            res.emplace_back(que.front());
        }
        return res;
    }
};

【收获与时长】2h


347.前 K 个高频元素 (一刷至少需要理解思路)

【链接】(文章,视频,题目)

大/小顶堆的应用, 在C++中就是优先级队列

本题是 大数据中取前k值 的经典思路,了解想法之后,不算难。

题目链接/文章讲解/视频讲解:代码随想录

【第一想法与实现(困难)】

  • map<int, int>,值,出现次数

  • 对频率进行排序,没想好,尝试用sort函数加上lambda表达式

【看后想法】

  • 本质上要做三件事

    • 对值出现的次数做统计,无序映射:值,出现次数

    • 根据出现次数做排序,

    • 选取前k大的值

  • 排序选前k大的,使用小顶堆,把小的堆顶移除,剩余k个就是前k大的

  • 优先级队列的反直觉的定义:当运算符为less,表示大顶堆。记忆上,方便自己理解:

  • 优先级队列尾部,1<2<3<4,头部

【实现困难】

  • 自定义仿函数(函数对象),其实是一个排序类,注意细节

    • class结尾分号;

    • public:

    • bool operator() (const type& lhs, const type& rhs) {…}

【自写代码】

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 使用优先级队列
        class mygreator{
        public:
            bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
                return lhs.second > rhs.second;
            }
        };
        // 统计每个值的出现次数
        std::unordered_map<int, int> umap; // 值,出现次数
        for (const auto& num : nums) {
            umap[num]++;
        }
        // 实现小顶堆,找前k大
        std::priority_queue<pair<int, int>, vector<pair<int, int>>, mygreator> pq;
        for (auto iter = umap.begin(); iter != umap.end(); iter++) {
            pq.push(*iter);
            if (pq.size() > k) {
                pq.pop();
            }
        }
        vector<int> res;
        while (!pq.empty()) {
            res.emplace_back(pq.top().first);
            pq.pop();
        }
        return res;
    }
};

【收获与时长】

  • 2h25m
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值