• 代码随想录:栈与队列


    此前了解的栈与队列和堆

    python中的大小堆 heapq - PiaYie - 博客园 (cnblogs.com)

    队列 - PiaYie - 博客园 (cnblogs.com)

    树的遍历 - PiaYie - 博客园 (cnblogs.com)

    队列是先进先出,栈是先进后出,堆是满足特定结构

    此外队列还有双端队列

    栈和队列是STL(C++标准库)里面的两个数据结构,C++标准库有很多版本,要知道自己使用标准是啥,和堆栈的底层实现

    (1条消息) 数据结构:堆栈的区别_SongXJ的博客-CSDN博客_堆栈区别

    关于栈

    Stack in C++ STL - GeeksforGeeks

    栈是C++标准库中的一种数据结构,栈是一种后进先出(LIFO,last in first out)类型的容器适配器,即在一端(顶部)添加一个新元素,然后从(顶部)删除一个元素。Stack使用封装的vector或deque(默认情况下)或list(顺序容器类)对象作为其底层容器,提供一组特定的成员函数来访问其元素。

    堆栈的语法:为了创建堆栈,我们必须在代码中包含<stack>头文件。

    The functions associated with stack are: 
    empty() – Returns whether the stack is empty – Time Complexity : O(1) 
    size() – Returns the size of the stack – Time Complexity : O(1) 
    top() – Returns a reference to the top most element of the stack – Time Complexity : O(1) 
    push(g) – Adds the element ‘g’ at the top of the stack – Time Complexity : O(1) 
    pop() – Deletes the top most element of the stack – Time Complexity : O(1) 

    栈四个问题

    C++中stack 是容器么?

    是一种数据结构,STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)

    我们使用的stack是属于那个版本的STL?

    SGI-STL

    C++ STL版本有哪些? (biancheng.net)

    我们使用的STL中stack是如何实现的?

    SGI STL栈的底层实现是用双端队列deque实现的。

    P.s 栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能),默认是deque

    • 我们也可以指定vector为栈的底层实现,初始化语句如下:
    std::stack<int, std::vector<int> > third;  // 使用vector为底层容器的栈
    • 队列也可以指定list 为其底层实现,初始化queue的语句如下:
    std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列

    stack 提供迭代器来遍历stack空间么?

    不提供,只有特定的方法 top push pop empty等等

    关于队列

    Queue in C++ Standard Template Library (STL) - GeeksforGeeks

    队列是一种采用先进先出(FIFO, first in first out)方式操作的容器适配器。元素被插入到后面(末端),并从前面删除。队列使用封装的deque或list(顺序容器类)对象作为其底层容器,提供一组特定的成员函数来访问其元素。

    队列四个问题

    C++中queue是容器么?

    是容器适配器

    我们使用的queue是属于那个版本的STL?

    SGI-STL

    我们使用的STL中queue是如何实现的?

    默认是双端队列deque

    queue 提供迭代器来遍历queue空间么?

    不提供,只有一些方法,Methods of Queue are: 

    MethodDefinition
    queue::empty() Returns whether the queue is empty.
    queue::size() Returns the size of the queue.
    queue::swap() Exchange the contents of two queues but the queues must be of the same type, although sizes may differ.
    queue::emplace() Insert a new element into the queue container, the new element is added to the end of the queue.
    queue::front() Returns a reference to the first element of the queue.
    queue::back() Returns a reference to the last element of the queue.
    queue::push(g)  Adds the element ‘g’ at the end of the queue.
    queue::pop()  Deletes the first element of the queue.

     

     

     

     

     

     

     

     

     

     

     

    用栈实现队列

    232. 用栈实现队列 - 力扣(LeetCode) (leetcode-cn.com)

    使用栈实现队列的下列操作:

    push(x) -- 将一个元素放入队列的尾部。
    pop() -- 从队列首部移除元素。
    peek() -- 返回队列首部的元素。
    empty() -- 返回队列是否为空

    思路:c++中栈的操作有 pop() push() top() empty() size(),要实现先进先出的队列的基本方法pop() empty() push() peek()?返回队列开头的元素,那么方法之一是使用两个栈,一个作为输入栈,一个作为输出栈

    要注意将元素从输入栈到输出栈加入的时机。需要等到输出栈为空的那一刻,并且要讲输入栈的所有元素pop到输出栈。

    当两个栈都为空时,该队列为空。

    peek可以复用pop函数。

    class MyQueue {
    public:
        stack<int> stackIn;
        stack<int> stackOut;
        MyQueue() {
    
        }
        
        void push(int x) {
            stackIn.push(x);
        }
        
        int pop() {
            //只有当输出栈空时,才将输出栈的所有元素导入输出栈
            if(stackOut.empty()){
                while(!stackIn.empty()){
                    stackOut.push(stackIn.top());
                    stackIn.pop();
                }
            }
            //pop要return最早进栈的元素
            int res = stackOut.top();
            stackOut.pop();
            return res;
        }
        
        int peek() {
            int res = this->pop();
            stackOut.push(res);
            return res;
        }
        
        bool empty() {
            return stackIn.empty() && stackOut.empty();   
        }
    };

    用队列实现栈

     225. 用队列实现栈 - 力扣(LeetCode) (leetcode-cn.com)

    队列有接口pop()不返回元素只管pop push() empty() front(), 或许还有双端队列deque,来实现栈操作 pop() push() top() empty()

    思路:

    1. 用双端队列deque只用一端的接口
    2. 只用单向队列的话怎么做呢(可以用两个队列来模拟栈,不像上一题一个做输入一个做输出,这里模拟的两个队列一个仅仅用来做备份)
    3. 只用一个单向队列怎么做?(一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出队首元素)

    使用deque解法:

    class MyStack {
    public:
        deque<int> dequeList;
        MyStack() {
    
        }
        
        void push(int x) {
            dequeList.push_front(x);
        }
        
        int pop() {
            int res = dequeList.front();
            dequeList.pop_front();
            return res;
        }
        
        int top() {
            return dequeList.front();
        }
        
        bool empty() {
            return dequeList.empty();
        }
    };

    使用一个queue实现stack:

    class MyStack2 {
    public:
        queue<int> queueList;
        MyStack2() {
    
        }
        
        void push(int x) {
            queueList.push(x);
        }
        
        int pop() {
            int lenSize = queueList.size() -1;
            while(lenSize--){
                queueList.push(queueList.front());
                queueList.pop();
            }
            int res = queueList.front();
            queueList.pop();
            return res;
        }
        
        int top() {
            //返回队尾
            return queueList.back();
        }
        
        bool empty() {
            return queueList.empty();
        }
    };

    有效的括号

    20. 有效的括号 - 力扣(LeetCode) (leetcode-cn.com)

    给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

    有效字符串需满足:

    左括号必须用相同类型的右括号闭合。
    左括号必须以正确的顺序闭合。

    示例 4:

    输入:s = "([)]"
    输出:false
    示例 5:

    输入:s = "{[]}"
    输出:true

    思路:首先分析一下不匹配的情况,理清思路

    carl哥说只要覆盖三种不匹配模式就可以:

    • 字符串里左方向的括号多余了

    • 括号没有多余,但是 括号的类型没有匹配上

    • 字符串里右方向的括号多余了

     题解:

    1. 如果遇到了左括弧,肯定要匹配一个其右括弧,而且后遇到的要先匹配,即后进先出,用栈!
    2.  碰到了右括弧,就要往栈顶找匹配,如果匹配到了就把栈顶删了,继续往下;如果没有匹配到就要返回false
    3. 如果找到一个右括弧,但是之前没有对应的左括弧,即栈为空或者和栈顶右括弧不匹配(相等),返回false
    4. 最后匹配完了,栈一定为空,才能返回true!
    //正确匹配括号,且只有括号的字符串‘[](){}’
    class matchSolution {
    public:
        bool isValid(string s) {
            stack<int> st;
            for (int i = 0; i < s.size(); i++) {
                if (s[i] == '(') st.push(')');
                else if (s[i] == '{') st.push('}');
                else if (s[i] == '[') st.push(']');
                // 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
                // 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
                else if (st.empty() || st.top() != s[i]) return false;
                else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
            }
            // 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
            return st.empty();
        }
    };

    删除字符串中的所有相邻重复项

    1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode) (leetcode-cn.com)

    给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们

    在 S 上反复执行重复项删除操作,直到无法继续删除。

    在完成所有重复项删除操作后返回最终的字符串。答案保证唯一

    示例:

    • 输入:"abbaca"
    • 输出:"ca"

     思路:使用栈,入栈的元素和栈顶元素做对比,如果相同,忽略该元素,删除栈顶元素。最后返回栈中元素的反转

    class Solution {
    public:
        string removeDuplicates(string s) {
            stack<char> st;
            for(char ch: s){
                if(st.empty() || ch != st.top()){
                    st.push(ch);
                }else{
                    st.pop();
                }
            }
    
            //返回栈中元素的反转
            string res = "";
            while(!st.empty()){
                res += st.top();
                st.pop();
            }
            reverse(res.begin(),res.end());
            return res;
        }
    };

    上面代码在leetcode上的评分并不高。改进,直接把字符串当作栈,省去反转字符串的开销:

    class Solution {
    public:
        string removeDuplicates(string s) {
            string st = "";
            for(char ch: s){
                if(st.empty() || ch != st.back()){
                    st.push_back(ch);
                }else{
                    st.pop_back();
                }
            }
            return st;
        }
    };

    P.S.原来 string还有 empty()方法啊 还有back()方法啊 还有 pushpop_back()方法啊是

    逆波兰表达式求值

    150. 逆波兰表达式求值 - 力扣(LeetCode) (leetcode-cn.com)

    根据 逆波兰表示法,求表达式的值。

    • 有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
    • 注意 两个整数之间的除法只保留整数部分。
    • 可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

    示例 1:

    输入:tokens = ["2","1","+","3","*"]
    输出:9
    解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
    示例 2:

    输入:tokens = ["4","13","5","/","+"]
    输出:6
    解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
    示例 3:

    输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
    输出:22
    解释:该算式转化为常见的中缀算术表达式为:
    ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
    = ((10 * (6 / (12 * -11))) + 17) + 5
    = ((10 * (6 / -132)) + 17) + 5
    = ((10 * 0) + 17) + 5
    = (0 + 17) + 5
    = 17 + 5
    = 22

    也就是说,A  •  B 是按左算子A 右算子B 运算符• 展开的

    那么就和二叉树的后序遍历有点意思了。这其实叫做后缀表达式,即运算符写在后面

    逆波兰表达式主要有以下两个优点:

    • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。所以后缀表达式对计算机来说是非常友好的

    • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中(解题思路,dei就是这么简单)

    class Solution {
    public:
        int evalRPN(vector<string>& tokens) {
            stack<int> elementStack;
            for(int i=0;i<tokens.size();i++){
                if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/"){
                    int element1 = elementStack.top();
                    elementStack.pop();
                    int element2 = elementStack.top();
                    elementStack.pop();
                    if(tokens[i]=="+") elementStack.push(element2 + element1); //顺序应该是反过来,因为是栈的原因
                    if(tokens[i]=="-") elementStack.push(element2 - element1);
                    if(tokens[i]=="*") elementStack.push(element2 * element1);
                    if(tokens[i]=="/") elementStack.push(element2 / element1);
                    
                }else{
                    //因为是string,所以要转化为数
                    elementStack.push(stoi(tokens[i]));
                }
            }
            return elementStack.top();
        }
    };

    stoi()函数把string转成int

    滑动窗口最大值

    239. 滑动窗口最大值 - 力扣(LeetCode) (leetcode-cn.com)

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

    • 返回滑动窗口中的最大值。
    • 在线性时间复杂度内解决此题。

    思路:维护一个单调队列(长度不一定是k,只要包含窗中最大值就可以了),可以用deque来做,这样的话最大的出去的很方便找到第二大的元素。

    • 单调队列的头放着最大的元素(几个元素),尾巴位置用来加入新的元素
    • 如果窗口要移除的元素刚好单调队列中的最大值,即头位置的元素,就把头pop出去
    • 窗口往前移会新增一个元素,为了保证单调,切在尾巴back位置加入,要把小于这个新元素的尾部给pop出去,再在尾巴加新元素
    • 输出的话,当窗口移到第k个元素才输出最大值。
    class Solution {
    public:
        vector<int> maxSlidingWindow(vector<int>& nums, int k) {
            
            //滑动窗口  + 维护一个单调的队列
            deque<int> monoQueue; //c++中也有deque  常用这个才对
            vector<int> res;
            int n = nums.size();
            for(int i = -k+1;i<n-k+1;i++){
                int j = i+k-1;
                // 如果出窗口的刚好是上一轮的最大值
                if(i>0 && nums[i-1] == monoQueue.front()){
                    monoQueue.pop_front();
                }
                //保证单调的队列
                while(!monoQueue.empty() && monoQueue.back() <nums[j]){
                    monoQueue.pop_back();
                }
    
                monoQueue.push_back(nums[j]);
                if(i>=0){
                    res.push_back(monoQueue.front());
                }
            }
            return res;
    
        }
        
    };

    前 K 个高频元素

    347. 前 K 个高频元素 - 力扣(LeetCode) (leetcode-cn.com)

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

    • 可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
    • 你的算法的时间复杂度必须优于 $O(n \log n)$ , n 是数组的大小
    • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

     用字典不就完事了吗?统计完还要排序的,排序用快排?

    首先想到的就是map

    map可以实现统计功能,但是对频数进行排序还是挺麻烦的,有没有可能我统计完,排序也出来了?有 ------容器适配器:优先级队列

    可以理解为披着队列外衣的堆,常说的大小堆就是优先级队列

    如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。

    python中的大小堆 heapq - PiaYie - 博客园 (cnblogs.com)

    大顶堆:每次弹出最大的元素

    小顶堆:每次弹出最小的元素

    思路:使用小顶堆存频数。当堆中数据多与k组时,每次把频数最小的数据弹出,最后返回的k个数据就是前 K 个高频元// 时间复杂度:O(nlogk)

    // 空间复杂度:O(n)
    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;//不可以用greater<int> 因为比较的是一个,我们要根据pair<int,int>的第二个值比较
    // 用固定大小为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; } };
  • 相关阅读:
    ASCII码表记忆规律
    Live Photos原理
    FAAS -- Serverless
    wasm能力检测
    守则
    split分割文件
    个人开源项目:微服务全栈技术学习开源项目,涵盖Java及前端主流技术点
    采用React+Ant Design组件化开发前端界面(一)
    SpringBoot 2.0中SpringWebContext 找不到无法使用的问题解决
    [做全栈攻城狮]程序员带你学习安卓开发-安卓基础之网络编程 大汇总
  • 原文地址:https://www.cnblogs.com/PiaYie/p/15931762.html
Copyright © 2020-2023  润新知