代码随想录day10|232.用栈实现队列、225.用队列实现栈、20.有效的括号、1047.删除字符串中的所有相邻重复项

提示:DDU,供自己复习使用。欢迎大家前来讨论~


栈与队列Part01

了解栈与队列的内部实现机制

一、栈与队列理论基础

原理:

队列是先进先出,栈是先进后出。

在这里插入图片描述

栈和队列是STL(C++标准库)里面的两个数据结构。

C++标准库是有多个版本的,要知道我们使用的STL是哪个版本,才能知道对应的栈和队列的实现原理。

那么来介绍一下,三个最为普遍的STL版本:

  1. HP STL 其他版本的C++ STL,一般是以HP STL为蓝本实现出来的,HP STL是C++ STL的第一个实现版本,而且开放源代码。
  2. P.J.Plauger STL 由P.J.Plauger参照HP STL实现出来的,被Visual C++编译器所采用,不是开源的。
  3. SGI STL 由Silicon Graphics Computer Systems公司参照HP STL实现,被Linux的C++编译器GCC所采用,SGI STL是开源软件,源码可读性甚高。
    在这里插入图片描述

栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。

队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器, SGI STL中队列一样是以deque为缺省情况下的底部结构。

二、题目

题目一:232.用栈实现队列

leetcode题目链接

解题思路:

使用栈来模式队列的行为,需要使用到两个栈,一个进,一个出,这样才可以达到先进先出的队列的特点。

细节:

一定要懂得复用,功能相近的函数要抽象出来,抽象成一个好用的函数或者工具类,不要大量的复制粘贴,很容易出问题!

class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;

    MyQueue() {}

    void push(int x) { stIn.push(x); }

    int pop() {
        // 在输出栈为空时,添加元素
        while (stOut.empty()) {
            while (!stIn.empty()) {
                stOut.push(stIn.top());
                stIn.pop();
            }
        }
        int res = stOut.top();
        stOut.pop();
        return res;
    }

    int peek() {
        int res = this->pop();
        stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去。
        return res;
    }

    bool empty() { return stIn.empty() && stOut.empty(); }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */
  • 时间复杂度: push和empty为O(1), pop和peek为O(n)
  • 空间复杂度: O(n)

题目二: 225. 用队列实现栈*

leetcode题目链接

解题思路

2.1使用两个队列实现(惯性思维):

队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。

用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。

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--;
        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 size = que1.size();
        size--;
        while (size--) {
            // 将que1 导入que2,但要留下最后一个元素
            que2.push(que1.front());
            que1.pop();
        }

        int result = que1.front(); // 留下的最后一个元素就是要回返的值
        que2.push(
            que1.front()); // 获取值后将最后一个元素也加入que2中,保持原本的结构不变
        que1.pop();

        que1 = que2; // 再将que2赋值给que1
        while (!que2.empty()) {
            // 清空que2
            que2.pop();
        }
        return result;
    }

    /** Returns whether the stack is empty. */
    bool empty() { return que1.empty(); }
};
  • 时间复杂度: pop为O(n),其他为O(1)

  • 空间复杂度: O(n)

2.2 使用一个队列实现栈的操作(优化):

一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。

class MyStack {
public:
    queue<int> que;

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

    }

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

    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int size = que.size();
        size--;
        while (size--) { // 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部
            que.push(que.front());
            que.pop();
        }
        int result = que.front(); // 此时弹出的元素顺序就是栈的顺序了
        que.pop();
        return result;
    }

    /** Get the top element.
     ** Can not use back() direactly.
     */
    int top(){
        int size = que.size();
        size--;
        while (size--){
            // 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部
            que.push(que.front());
            que.pop();
        }
        int result = que.front(); // 此时获得的元素就是栈顶的元素了
        que.push(que.front());    // 将获取完的元素也重新添加到队列尾部,保证数据结构没有变化
        que.pop();
        return result;
    }

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

题目三:20. 有效的括号

leetcode题目链接

括号匹配是使用栈解决的经典问题。

解题思路:

先想清楚一共有多少种情况{}()[]

  • 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。

  • 第二种情况,括号没有多余,但是 括号的类型没有匹配上。

  • 第三种情况,字符串里右方向的括号多余了,所以不匹配。

细节技巧:

  1. 可以使用剪枝操作,如果字符串是奇数一定是由不匹配的。

  2. 在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

**成功匹配的条件**:字符串遍历完之后,栈是空的,就说明全都匹配了。

class Solution {
public:
    bool isValid(string s) {
        if (s.size() % 2 != 0) return false; // 剪枝操作
        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(']');
            else if (st.empty() || st.top() != s[i]) return false;//在不是左括号的情况下,还有元素的操作。
            else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
        }
        // 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
        return st.empty();
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

题目四:1047. 删除字符串中的所有相邻重复项

leetcode题目链接

解题思路:

  1. 匹配与消除:与匹配左右括号的问题类似,本题涉及到的也是匹配问题,但不同之处在于我们关注的是相邻元素的匹配,并在此基础上执行消除操作。
  2. 栈的应用:本题是使用栈来解决的经典问题。栈在这里充当了临时存储的角色,用于存放我们遍历过程中遇到的元素。
  3. 遍历与检查:在遍历字符串的过程中,我们利用栈来检查当前元素是否与栈顶元素相同。如果发现相邻元素相同,即执行消除操作。
  4. 结果的生成:遍历结束后,栈中剩余的元素将按照它们入栈的逆序排列。为了得到最终的正确顺序,我们需要将这些元素从栈中弹出,并进行一次反转操作。
  5. 最终结果:通过上述步骤,我们可以得到一个不含相邻重复元素的字符串,它既整洁又符合我们的要求。

代码如下:

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()); // 此时字符串需要反转一下
        return result;

    }
};
  • 时间复杂度: O(n)

  • 空间复杂度: O(n)

当然可以拿字符串直接作为栈,这样省去了栈还要转为字符串的操作。

class Solution {
public:
    string removeDuplicates(string S) {
        string result;
        for(char s : S) {
            if(result.empty() || result.back() != s) {
                result.push_back(s);
            }
            else {
                result.pop_back();
            }
        }
        return result;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1),返回值不计空间复杂度
  1. 递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数。

  2. 在企业项目开发中,尽量不要使用递归!在项目比较大的时候,由于参数多,全局变量等等,使用递归很容易判断不充分return的条件,非常容易无限递归(或者递归层级过深),造成栈溢出错误(这种问题还不好排查!


总结

  • 栈(Stack)和队列(Queue)是两种基本的抽象数据类型,它们在操作方式上有着本质的不同,但有趣的是,它们可以相互实现。
  • 栈在匹配问题中的经典使用(括号匹配)。
  • 在处理字符串问题时,栈提供了一种高效且直观的方法来执行各种操作。
  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值