代码随想录Day_9 栈与队列part01

### 理论基础

了解一下 栈与队列的内部实现机制,文中是以C++为例讲解的。
文章讲解:[https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html](https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html)

### 容器适配器(Container Adapters)
容器适配器是基于基本容器的封装,提供了特定的接口,限制了一些操作,专门用于实现特定的数据结构。它们不能直接实例化,而是基于某种底层容器实现。常见的容器适配器包括:
- **queue**:基于`deque`或`list`实现,提供**先进先出**(FIFO)队列的接口。
- **priority_queue**:基于`vector`实现,提供优先级队列的接口。
- **stack**:基于`deque`或`list`实现,提供**后进先出**(LIFO)堆栈的接口。
#### 1. deque(双端队列)

- **类别**:容器(Sequence Container)。
- **特点**:双端队列,支持在两端进行插入和删除操作,同时支持随机访问。
#### 2. queue(队列)

- **类别**:容器适配器(Container Adapter)。
- **特点**:先进先出(FIFO)队列,基于`deque`或`list`实现,只允许在一端插入,在另一端删除,不支持随机访问。
#### 3. priority_queue(优先队列)

- **类别**:容器适配器(Container Adapter)。
- **特点**:优先级队列,基于`vector`实现,按优先级顺序访问元素,不支持随机访问,只能访问优先级最高的元素。

### 232.用栈实现队列

大家可以先看视频,了解一下模拟的过程,然后写代码会轻松很多。
题目链接/文章讲解/视频讲解:[https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html](https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html)
![[Day_9_1.gif]]
1.思路;
使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈**一个输入栈,一个输出栈**,这里要注意输入栈和输出栈的关系。

在push数据的时候,只要数据放进输入栈就好,**但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入)**,再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。

最后如何判断队列为空呢?**如果进栈和出栈都为空的话,说明模拟的队列为空了。**

在代码实现的时候,会发现pop() 和 peek()两个函数功能类似,代码实现上也是类似的,可以思考一下如何把代码抽象一下。

2.代码:
```
class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;
    /** Initialize your data structure here. */
    MyQueue() {

    }
    /** Push element x to the back of queue. */
    void push(int x) {
        stIn.push(x);
    }

    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        // 只有当stOut为空的时候,再从stIn里导入数据(导入stIn全部数据)
        if (stOut.empty()) {
            // 从stIn导入数据直到stIn为空
            while(!stIn.empty()) {
                stOut.push(stIn.top());
                stIn.pop();
            }
        }
        int result = stOut.top();
        stOut.pop();
        return result;
    }
    //这边注意一下
    /** Get the front element. */
    int peek() {
        int res = this->pop(); // 直接使用已有的pop函数
        stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
        return res;
    }
    //输入栈和输出栈都要判断
    /** Returns whether the queue is empty. */
    bool empty() {
        return stIn.empty() && stOut.empty();
    }
};
```
### 225. 用队列实现栈

可能大家惯性思维,以为还要两个队列来模拟栈,其实只用一个队列就可以模拟栈了。

建议大家掌握一个队列的方法,更简单一些,可以先看视频讲解

题目链接/文章讲解/视频讲解:[https://programmercarl.com/0225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.html](https://programmercarl.com/0225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.html)

1.思路:
**用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用**,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
![[Day_9_2.gif]]
2.代码(两个队列):
```
class MyStack {
public:
    queue<int> que1;
    queue<int> que2; // 辅助队列,用来备份

    /** Initialize your data structure here. */
    MyStack() {

    }

    /** Push element x onto stack. */
    void push(int x) {
        que1.push(x);
    }

    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int size = que1.size();
        size--;//可以用size()来判断留下最后一个元素
        while (size--) { // 将que1 导入que2,但要留下最后一个元素
            que2.push(que1.front());
            que1.pop();
        }

        int result = que1.front(); // 留下的最后一个元素就是要返回的值
        que1.pop();
        que1 = que2;            // 再将que2赋值给que1,直接赋值简便
        while (!que2.empty()) { // 清空que2
            que2.pop();
        }
        return result;
    }

    /** Get the top element.
     ** Can not use back() direactly.
     */
    int top(){
        int result=this->pop();
        que1.push(result);
        return result;
    }

    /** Returns whether the stack is empty. */
    bool empty() {
        return que1.empty();
    }
};
```

代码(1个队列)(队首的元素移到队尾):
```
class MyStack {
public:
    queue<int> que1;

    /** Initialize your data structure here. */
    MyStack() {

    }

    /** Push element x onto stack. */
    void push(int x) {
        que1.push(x);
    }

    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int size = que1.size();
        size--;//可以用size()来判断留下最后一个元素
        while (size--) { // 将que1 导入que2,但要留下最后一个元素
            int tmp=que1.front();
            que1.pop();
            que1.push(tmp);
        }
        int result = que1.front(); // 留下的最后一个元素就是要返回的值
        que1.pop();
        return result;
    }

    /** Get the top element.
     ** Can not use back() direactly.
     */
    int top(){
        int result=this->pop();
        que1.push(result);
        return result;
    }

    /** Returns whether the stack is empty. */
    bool empty() {
        return que1.empty();
    }
};
```


### 20. 有效的括号

讲完了栈实现队列,队列实现栈,接下来就是栈的经典应用了。

大家先自己思考一下 有哪些不匹配的场景,在看视频 我讲的都有哪些场景,落实到代码其实就容易很多了。

题目链接/文章讲解/视频讲解:[https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html](https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html)

1.思路:
**括号匹配是使用栈解决的经典问题。** 括号匹配的顺序跟栈的FILO顺序一样
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false

那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。

但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
2.代码:
```
class Solution {
public:
    bool isValid(string s) {
        if (s.size() % 2 != 0) return false; // 如果s的长度为奇数,一定不符合要求
        stack<char> 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. 删除字符串中的所有相邻重复项

栈的经典应用。

要知道栈为什么适合做这种类似于爱消除的操作,因为栈帮助我们记录了 遍历数组当前元素时候,前一个元素是什么。

题目链接/文章讲解/视频讲解:[https://programmercarl.com/1047.%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.html](https://programmercarl.com/1047.%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.html)

代码:
```
class Solution {
public:
    string removeDuplicates(string S) {
        stack<char> st;
        for (char s : S) {
            if (st.empty() || s != st.top()) {
                st.push(s);
            } else {
                st.pop(); // s 与 st.top()相等的情况
            }
        }
        string result = "";
        while (!st.empty()) { // 将栈中元素放到result字符串汇总
            result += st.top();
            st.pop();
        }
        reverse (result.begin(), result.end()); // 此时字符串需要反转一下
        //整体时间复杂度是 O(n)(遍历栈元素) + O(n)(反转字符串),即 O(n)。
        //不好的方法,因为+=在末尾添加元素时,如果字符串内部已经有足够的空间,通常不会触发新的内存分配。而+每次在开头添加元素时,都会创建一个新的字符串对象,并复制整个 `result`,这涉及大量的内存分配和数据复制。
        while (!st.empty()) { // 将栈中元素放到result字符串汇总
            result = st.top()+result;
            st.pop();
        }//O(n)(遍历栈元素),每次操作都是 O(n) 的复制操作,总的时间复杂度是 O(n^2)
        return result;

    }
};
```

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值