Studying-代码随想录训练营day12| 150.逆波兰表达式求值、239.滑动窗口最大值、347.前K个高频元素、总结

第11天周末休息一天,第12天(ง •_•)ง,编程语言:C++

目录

150.逆波兰表达式

239.滑动窗口最大值

347.前K个高频元素

总结:


150.逆波兰表达式

文档讲解:代码随想录逆波兰表达式

视频讲解:手撕逆波兰表达式

题目:

学习:数据结构中的经典例题,后缀表达式计算(逆波兰表达式),后缀表达式的结构有助于计算机进行计算,不同于中缀表达式,需要括号来区别优先运算。后缀表达式通过栈结构能够实现无需括号的运算顺序。

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> ans;
        for (string c : tokens) {
            if (c == "+" || c == "-" || c == "*" || c == "/") {
                int num1 = ans.top();
                ans.pop();
                int num2 = ans.top();
                ans.pop();
                if (c == "+") ans.push(num2 + num1);
                if (c == "-") ans.push(num2 - num1);
                if (c == "*") ans.push(num2 * num1);
                if (c == "/") ans.push(num2 / num1);
            }
            else {
                //将字符串转化为数值类型,stoi将字符串转化为int类型,stoll将字符串转化为longlong类型
                ans.push(stoi(c));
            }
        }
        return ans.top();
    }
};

239.滑动窗口最大值

文档讲解:代码随想录滑动窗口最大值

视频讲解:手撕滑动窗口最大值

题目: 

学习: 

1.构造一个单调队列:在单调队列中,每次窗口移动的时候,调用que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。这个单调队列在c++中没有现成的数据结构,因此需要自行设计。同时队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。

代码:使用deque作为底层容器最为合适。

//时间复杂度O(n)
//空间复杂度O(K)
class Solution {
private:
    class MyQueue { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);

        }
        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return que.front();
        }
    };
public:
    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()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};

2.直接使用deque作为一个队列,进行滑动窗口维护的操作。

代码:

//时间复杂度O(n)
//空间复杂度O(k)
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> ans; //创建滑动窗口
        vector<int> res; //返回答案数组
        //初始化滑动窗口,把k个元素导入,并维护其中的最大值
        for(int i = 0; i < k; i++) {
            //当队列不为空,且新加入的值大于最后一个数值的时候,把值排出,维护最大值
            while (!ans.empty() && nums[i] > ans.back()) {
                ans.pop_back();
            }
            ans.push_back(nums[i]);
        }
        //加入第一个窗口的最大值
        res.push_back(ans.front());

        for(int i = k; i < nums.size(); i++) {
            //如果当前要排除的值,是队列中的最大值,则说明该值需要被排除
            if (nums[i - k] == ans.front()) {
                ans.pop_front();
            }
            cout << ans.size() << endl;
            //加入新的值
            while (!ans.empty() && nums[i] > ans.back()) {
                ans.pop_back();
            }
            ans.push_back(nums[i]);
            res.push_back(ans.front());
        }
        return res;
    }
};

3. C++中可以使用multiset作为单调队列的实现。多重集合(multiset)可以有序存储元素,且允许存在相等的元素。multiset提供了*rbegin(),可以直接获取窗口最大值。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        multiset<int> st;
        vector<int> ans;
        for (int i = 0; i < nums.size(); i++) {
            if (i >= k) st.erase(st.find(nums[i - k]));
            st.insert(nums[i]);
            if (i >= k - 1) ans.push_back(*st.rbegin());
        }
        return ans;
    }
};

收获:

  1. 本题中构造的单调队列设计的pop和push结构,仅适用于本题。单调队列不是一成不变的,不同环境下可以采取不同的写法,只需保证队列里单调递减或递增的原则,就叫做单调队列。
  2. C++中deque是stack和queue默认的底层实现容器,deque是可以两边扩展的,而且deque里的元素并不是严格的连续分布的。

347.前K个高频元素

文档讲解:代码随想录前K个高频元素

视频讲解:手撕前K个高频元素

题目:

学习:本题主要设计三个部分的内容:1.统计元素出现频率;2.对频率进行排序;3.找出前k个高频元素。

1.统计元素的频率:这可以采用一个map结构来收集所有的元素,key表示元素的值,value表示元素的频率,即出现的次数。

2.对频率进行排序:在这里对频率进行排序,优先可以考虑采取优先级队列(priority_queue)的方式。优先级队列本质是一个堆,堆分为大顶堆和小顶堆。由于我们只需要前k个高频元素,堆内只需要存储k个元素,将频率小的元素弹出,因此可以采取小顶堆的方式。统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

3.找出前k个高频元素:之后通过遍历小顶堆里面的元素,就可以取出前k个高频元素。

//时间复杂度O(nlogk) (堆的排序的时间复杂度为klogk)
//空间复杂度O(n)
class Solution {
public:
    class comparision {
    public:
        bool operator() (const pair<int, int>& lhs, const pair<int, int>& rhs) {
            //比较value值,即频率值
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //统计数组中的元素个数和频率
        unordered_map<int, int>  map;
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }
        
        //构建小顶堆,对频率进行排序
        priority_queue<pair<int, int>, vector<pair<int, int>>, comparision> que;
        //遍历map
        for (auto it = map.begin(); it != map.end(); it++) {
            que.push(*it);
            //只保存前k个频率的元素
            if(que.size() > k) {
                que.pop();
            }
        }
        //去除小顶堆的前k个元素
        vector<int> result;
        for(int i = 0; i < k; i++) {
            result.push_back(que.top().first);
            que.pop();
        }
        return result;
    }
};

 注意:本题要注意建堆时,左大于右会建立小顶堆,右大于左建立大顶堆。与快排是相反的,快排return left > right是从大到小,return left < right是从小到大。这与优先级队列的源码实现有关。


总结:

在栈与队列的使用中,特别要注意栈与队列的底层实现。用栈实现队列,用队列实现栈来考察了栈与队列的基本操作。括号匹配问题、字符串去重问题、逆波兰表达式问题系统讲解了栈在系统中的应用,以及使用技巧。滑动窗口最大值,以及前K个高频元素介绍了两种队列:单调队列和优先级队列,这是特殊场景解决问题的利器。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值