代码随想录算法训练营第11天 | LeetCode150.逆波兰表达式求值、LeetCode239.滑动窗口最大值、LeetCode347.前k个高频元素

目录

LeetCode150.逆波兰表达式求值

LeetCode239.滑动窗口最大值

LeetCode347.前k个高频元素

1、辅助数组法

2、优先级队列法


LeetCode150.逆波兰表达式求值

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

思路:这道题考察对栈的使用,当遍历元素为运算符时需要将栈中的两个操作数弹出,运算完结果再压入栈,直到循环结束,将栈里面仅剩的一个元素保存输出即可。下面提供了两种方式,一种是自定义将字符串类型的栈中的元素转换为整型类的变量,一种是直接使用内置函数转换。

//将栈里面的字符串转换为整型int,使用long返回防止超限
    long stringToInt(string& s){
        long result = 0;
        bool minus = 0;
        int index = 0;
        if(s[0] == '-'){
            index = 1;
            minus = 1;
        }//当是负数时设置minus=1标志位
        int size = s.size();
        if(minus == 1) size --;//负数的话将其当成正数处理,然后再以负数返回
        for(; index < s.size(); index ++, size --){
            result += (s[index] - '0') * std::pow(10, size - 1);
        }
        if(minus == 1){
            return 0 - result;
        }else{
            return result;
        }
    }
    int evalRPN(vector<string>& tokens) {
        stack<string> st;
        int op1,op2;
        int result = 0;
        for(int i = 0; i < tokens.size(); i ++){
            if(tokens[i] == "+"){
                op1 = stringToInt(st.top());
                //当遇到对应操作符时取栈顶第一、二操作数
                //注意操作顺序
                st.pop();
                op2 = stringToInt(st.top());
                st.pop();
                st.push(to_string(op2 + op1));
                continue;
            }else if(tokens[i] == "-"){
                op1 = stringToInt(st.top());
                st.pop();
                op2 = stringToInt(st.top());
                st.pop();
                st.push(to_string(op2 - op1));
                continue;
            }else if(tokens[i] == "*"){
                op1 = stringToInt(st.top());
                st.pop();
                op2 = stringToInt(st.top());
                st.pop();
                st.push(to_string(op2 * op1));
                continue;
            }else if(tokens[i] == "/"){
                op1 = stringToInt(st.top());
                st.pop();
                op2 = stringToInt(st.top());
                st.pop();
                st.push(to_string(op2 / op1));
                continue;
            }else{
                st.push(tokens[i]);
            }
        }
        string str = st.top();//最后统一取栈里面的最后一个元素,将其转换位整型数返回即可
        result = stringToInt(str);
        return result;
    }
int evalRPN(vector<string>& tokens) {
        stack<int> st;
        int op1,op2;
        for(int i = 0; i < tokens.size(); i ++){
            if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] =="*" || tokens[i] == "/"){
                op1 = st.top();
                st.pop();
                op2 = st.top();
                st.pop();
                //当遇到对应操作符时取栈顶第一、二操作数
                //注意操作顺序v
               if(tokens[i] == "+") st.push(op2 + op1);
               if(tokens[i] == "-") st.push(op2 - op1);
               if(tokens[i] == "*") st.push(op2 * op1);
               if(tokens[i] == "/") st.push(op2 / op1);
            }else{
                st.push(stoi(tokens[i]));//将字符串转换为int型整数
            }
        }
        int result = st.top();
        return result;
    }

时间复杂度:O(n)

空间复杂度:O(n)

LeetCode239.滑动窗口最大值

 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值

思路:这道题采用单调队列,所谓单调不是指将元素全部排好序,如果是这样,那就直接排序就好了,没必要再采用单调队列法了。

这里是使用单调队列的整体思想,因为其具体实现与具体情况有关,并没有固定写法。

这里采用了deque作为底层的单调队列的实现容器,deque是可以两边进两边出的一种队列。

在这里主要是实现一种队列,对于窗口的窗尾元素删除,对窗头元素加入,然后还有是将每个窗口区间的最大值保存其值,主要完成这三件事。

对于窗口元素的删除,是在窗口元素与单调队列的队首元素相等时删除,(因为当不是队首时,会因为其值比其他元素小,在添加进队列时早就删除);

对于窗头元素的添加,将所有小于它的单调队尾元素删除队列,直到遇见比起大的。每个元素都会进入队列,出队列,但什么时候出,就看时机了。

对于保存每个窗口区间最大值,每次只需在删除、添加后,将单调队列首部元素保存即可,因为这必然是目前窗口内最大的。

我们的目的不是将窗口内每个元素排序,而是每次试图找到能够成为最大值的元素,将其加入,其他的在加入后,也会因为其太小而被后续删除。

class Solution {
private:
    class MyQueue{
    public:
    deque<int> que;//使用deque充当单调队列

    //自定义pop函数,当窗口移动时,如果说当前元素的前第k-3个元素为单调队列的队头元素,直接pop掉,
    //因为窗口之后算最大值已经使用不到它了,有了一个新区间,即使它很大,
    void pop(int value){
        if(!que.empty() && value == que.front()){
            que.pop_front();
        }
    }

    //自定义push函数,当要加入的值比单调队列里面的后面的元素大时,将它们pop_back()掉,
    //说明在窗口移动至应该添加该元素时,之前的比其小的元素显然在其存在这个队列中时,不再是最大的值了,
    //否则的话,直接将其加入
    void push(int value){
        while(!que.empty() && value > que.back()){
            que.pop_back();
        }
        que.push_back(value);//每个元素都要加,但是加之前先将其他小元素从单调队列删除
    }

    int front(){
        return que.front();//返回单调队列的队头元素
    }

};
public:
    
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> res;
        for(int i = 0; i < k; i ++){
            que.push(nums[i]);  //先将前k个元素加入到单调队列中
        }
        res.push_back(que.front());//将前k个元素的最大值加入到res中
        for(int i = k; i < nums.size(); i ++){
            que.pop(nums[i - k]);//首先对滑出去的之前的窗头元素删除
            que.push(nums[i]);//加入新滑入的元素
            res.push_back(que.front());//加入该元素后,说明内部已经比较完毕,往res中加入单调队列最大值front处的值
        }
        return res;
    }
};

时间复杂度:O(n)

空间复杂度:O(k)

这里还有一种方式是使用multiset,因为其本身是有序,并且允许重复值的一种set类型,每次只需将窗尾元素删除,将窗头元素加入,它就自动排序,然后找到其最大值加入即可。但是时间消耗比上面的单调队列来说要复杂,别看其代码精简,erase函数和find函数都是时间复杂度为O(n)的,所以该方法接近时间复杂度为O(n^2)。

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        multiset<int> mset;
        vector<int> res;
        for(int i = 0; i < nums.size(); i ++){
            if(i >= k) mset.erase(mset.find(nums[i - k]));
            //注意这里删除需要使用find方法删除找到的第一个元素,
            //否则可能因为编译器和STL的实现的不同,将所有找到的元素都删除了,会出错
            mset.insert(nums[i]);
            if(i >= k - 1) res.push_back(*mset.rbegin());
        }
        return res;
    }

LeetCode347.前k个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 

思路:这道题有两种解法,一种是辅助数组法,一种是优先级队列法

1、辅助数组法

说是辅助数组,起始是辅助map,大体思路就是先使用一个key不重复的map统计情况,然后用一个有序的且key值允许重复的map将统计情况中的key和value互换,这样排好序后,从后面开始,逆着找,则能找到前k个出现频率高的元素。

vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> mp;//key不可以重复
        multimap<int,int> mp_r;//key可以重复
        vector<int> res;
        for(int item : nums){
            mp[item] ++;//将数组情况放入
        }
        for(auto it = mp.begin(); it != mp.end(); it ++){
            mp_r.insert(make_pair(it -> second, it -> first));
            //将mp中的key和value值反过来放在mp_r中
        }
        for(auto iter = mp_r.rbegin(); k > 0; iter ++, k --){
            res.push_back(iter -> second);//倒序找到出现频率前k高的元素
        }
        return res;
    }

时间复杂度:O(n)

空间复杂度:O(n)

2、优先级队列法

优先级队列是一种完全二叉树,根据其实现情况可以分为最大堆和最小堆情况。

本题采用的是实现最小堆情况,因为最大堆每次弹出的都是当前最大值,后面没有办法返回题目要求的内容。

当使用了优先级队列后,维护队列的大小为k个,根据自定义的比较类实现它们按照出现次数的多少排序,最后统计完成,输出即可。

//自定义比较类
    class MyComparison{
    public:
        bool operator()(const pair<int, int>& left, const pair<int, int>& right){
            return left.second > right.second; //构造较小堆
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> mp; //key不重复
        for(int item: nums){
            mp[item] ++; //统计频率
        }
        priority_queue<pair<int, int>, vector<pair<int, int>>, MyComparison> pri_que; //定义优先队列

        for(unordered_map<int, int>::iterator it = mp.begin(); it != mp.end(); it ++){
            pri_que.push(*it);//将mp中的统计频率放入优先队列
            if(pri_que.size() > k){//维护优先队列的大小为k
                pri_que.pop();
            }
        }
        vector<int> res(k);
        for(int i = 0; i < k; i ++){
            res[i] = pri_que.top().first;//将优先队列里面的统计对的统计元素保存
            pri_que.pop();
        }
        return res;
    }

时间复杂度:O(nlogk)(排序是logk时间复杂度,整个过程是nlogk)

空间复杂度:O(n)

感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。

如果有什么问题欢迎评论区讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值