「代码随想录算法训练营」第十天 | 栈与队列 part2

150. 逆波兰表达式求值

题目链接:https://leetcode.cn/problems/evaluate-reverse-polish-notation/
题目难度:中等
文章讲解:https://programmercarl.com/0150.逆波兰表达式求值.html
视频讲解:https://www.bilibili.com/video/BV1kd4y1o7on
题目状态:多次修改 bug 后通过

个人思路:

通过进行解决。
先判断队列中的元素是否为数字类型。若为数字类型,压入栈;若不是数字类型,则肯定是运算符,使用switch结构进行运算,将栈中的头两个元素弹出,进行运算符运算,运算后将结果在压入栈中。最后返回栈顶元素。

修改 bug 的过程

  1. 栈的空间:当栈内有两个或两个以上的元素的时候才能进行运算符运算。
  2. 符号判断:在判断队列元素是否为数字的时候,忘记还有负数这一项,需要先判断元素的第一个元素是否为-且元素是否含有多个元素,若是,则从元素中的下一个元素判断是否为数字;若不是,则直接判断。

代码实现:

class Solution {
public:
    bool isNum(const string &s) {
        if(s[0] == '-' && s.size() > 1) {
            for(int i = 1; i < s.size(); ++i) {
                if(!isdigit(s[i])) return false;
            }
        } else if(s[0] == '-' && s.size() == 1) {
            return false;
        } else {
            for(auto &elem: s) {
                if(!isdigit(elem)) return false;
            }
        }
        return !s.empty();
    }

    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(auto &token: tokens) {
            if(isNum(token)) {
                st.push(stoi(token));
            }
            else if(!st.empty() && st.size() >= 2){
                int op1 = st.top(); st.pop();
                int op2 = st.top(); st.pop();
                int res = 0;
                switch(token[0]) {
                    case '+': res = op2 + op1; break;
                    case '-': res = op2 - op1; break;
                    case '*': res = op2 * op1; break;
                    case '/': res = op2 / op1; break;
                }
                st.push(res);
            }
        }
        return st.top();
    }
};

239. 滑动窗口最大值

题目链接:https://leetcode.cn/problems/sliding-window-maximum/
题目难度:困难
文章讲解:https://programmercarl.com/0239.滑动窗口最大值.html
视频讲解:https://www.bilibili.com/video/BV1XS4y1p7qj
题目状态:有思路,但败在了超时上,学习到了单调队列

思路:

一开始想,啊?困难题就这?根本难不倒我,最后却在编译的过程中败在了超时上,而且毫无思路去优化,最后学习了单调数列,利用单调数列解决这个问题异常轻松。

先看动图理解以下思路:

单调队列的构建:

  • 首先创建一个MyQue类来实现单调函数,分别有三个函数:pop()push()front()
  • push():这个不是简单的 push,当单调队列中存在的元素小于要 push 的元素,就需要将它们都弹出队列(使用 pop_back(),因为从队列的back开始遍历的),而把要 push 的元素 push 进来。此时,单调队列中的队头就是 push 进来的最大元素。
  • pop():当要弹出队列元素的时候,要弹出的元素小于队头元素时,表明这个元素早已在 push 的时候已经被弹出队列,不需要进行任何操作,只有当弹出元素和队头元素相等的时候,才需要把队头元素弹出(使用pop_front(),因为队头在队列的front处)。
  • front():这个就是返回单调队列的队头元素,也是此时队列中最大的一个元素。

通过构建的单调队列实现滑动窗口中的最大值,窗口每滑动一次,就需要把对应的左边元素从单调队列中pop出来,在把对应的右边的元素push到单调队列中去。每滑动一次,就使用一个数组去记录单调队列中的队头元素(因为这个元素时此时滑动窗口中的最大值),最后返回数组即可。

代码实现:

class Solution {
public:
    class MyQueue {
    public:
        void pop(int value) {
            if(!_que.empty() && value == _que.front()) _que.pop_front();
        }
        void push(int value) {
            while(!_que.empty() && value > _que.back()) _que.pop_back();
            _que.push_back(value);
        }
        int front() {
            return _que.front();
        }
    private:
        deque<int> _que;
    };

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue mq;
        vector<int> res;
        for(int i = 0; i < k; ++i) {
            mq.push(nums[i]);
        }
        res.push_back(mq.front());
        for(int i = k; i < nums.size(); ++i) {
            mq.pop(nums[i - k]);
            mq.push(nums[i]);
            res.push_back(mq.front());
        }
        return res;
    }
};

347. 前 K 个高频元素

题目链接:https://leetcode.cn/problems/top-k-frequent-elements/
题目难度:中等
文章讲解:https://programmercarl.com/0347.前K个高频元素.html
视频讲解:https://www.bilibili.com/video/BV1Xg41167Lz
题目状态:有思路,通过 ChatGPT 优化代码后通过

个人思路:

创建一个map,来存储字符串中出现的每个元素对应的次数,然后对map的值进行排序,获得前k个元素的键,前k个键就是字符串中前k个高频元素。

代码实现:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> countMap;
        for(auto &num : nums) countMap[num]++;

        vector<pair<int, int>> freq(countMap.begin(), countMap.end());
        sort(freq.begin(), freq.end(), [](const pair<int, int> &a, const pair<int, int> &b){ return a.second > b.second; });

        vector<int> res;
        for(int i = 0; i < k; ++i) {
            res.push_back(freq[i].first);
        }
        return res;
    }
};

优先级队列的思想:

通过小顶堆记录前k个频率的元素,每次小顶堆会将最小的元素弹出,最后小顶堆里积累的就是前k个最大元素。

代码实现(有点没看懂):

class Solution {
public:
    // 小顶堆
    class mycomparison {
    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; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};
posted @ 2024-07-13 10:47  云雀AC了一整天  阅读(18)  评论(0编辑  收藏  举报