【数据结构】栈和队列常见题目


队列:先进先出 栈:后进先出

队列:先进先出 栈:后进先出

有效的括号

https://leetcode.cn/problems/valid-parentheses/

做法:遍历字符串

1.当前字符是左括号:进栈

2.当前字符是右括号:出栈顶元素和当前字符比较是否匹配

  • 特殊情况:如果此时栈为空,那么说明不匹配

3.最后如果栈为空,说明是匹配的,否则是不匹配的

class Solution {
    
    
public:
    bool isValid(string s) {
    
    
        stack<char> st;
        //遍历字符串,碰到左括号:进栈   碰到右括号:出栈顶元素判断是否和该右括号匹配
        for(auto& ch:s)
        {
    
    
            if(ch == '(' || ch == '[' || ch == '{') 
            {
    
    
                st.push(ch);
            }
            else 
            {
    
    
                //如果栈为空,说明括号是不匹配的
                if(st.empty()) return false;
                //出栈顶元素和当前右括号匹配
                char top = st.top();
                st.pop();
                if( ch ==')' && top != '('  || ch == ']' && top != '[' ||ch == '}' && top != '{')
                    return false; 
            }
        }
        return st.empty(); //如果最后栈为空,说明括号是匹配的
    }
};

用队列实现栈

https://leetcode-cn.com/problems/implement-stack-using-queues/

image-20230818161844459

两个队列实现栈

队列:先进先出 栈:后进先出

两个队列:

  • NonEmptyQueue:表示存放数据的队列,记为A
  • EmptyQueue:表示空队列,记为B

插入元素:直接往A队列插入

弹出元素:将A队列的元素都插入到B队列当中,直到A队列只剩下一个元素,此时这个元素就相当于是栈顶元素。然后A队列和B队列交换,保证B队列是空的

例如:插入顺序为1 2 3 4 
A队列:1 2 3 4 
假设要栈顶元素:那么就算A队列队尾元素
假设要弹出栈顶元素:将A的元素放到B,直到A只有一个元素,然后弹出A队列剩余的一个元素就是栈顶元素,然后交换两个队列
    A:4   B:1 2 3  ==> A:1 2 3    B:

获取栈顶元素:本质就是A队列的队尾元素

判空:A队列和B队列为空 就是空

class MyStack {
    
    
public:
    MyStack() {
    
    

    }
    
    void push(int x) {
    
    
        NonEmptyQueue.push(x);//往不为空的队列插入数据
    }
    
    int pop() {
    
    
        //将不空队列的数据放到空队列当中,直到不空队列只有一个元素
        while(NonEmptyQueue.size() > 1)
        {
    
    
            EmptyQueue.push(NonEmptyQueue.front());
            NonEmptyQueue.pop();
        }
        int front = NonEmptyQueue.front();
        NonEmptyQueue.pop();
        NonEmptyQueue.swap(EmptyQueue);//交换两个队列,保证EmptyQueue为空队列
        return front;
    }
    
    int top() {
    
    
        return NonEmptyQueue.back();
    }
    
    bool empty() {
    
    
        return EmptyQueue.empty() && NonEmptyQueue.empty();
    }
private:
    queue<int> NonEmptyQueue;//不为空的队列
    queue<int> EmptyQueue;//空队列
};

一个队列实现栈

将队列类比为一个循环圈

插入元素:直接插入

弹出元素:将队列的前size-1个元素重新插入到队列当中,然后此时队头元素就是要弹出的===>队头元素就相当于是栈顶元素

获取栈顶元素:相当于是队尾元素

class MyStack {
    
    
public:
    MyStack() {
    
    

    }
    
    void push(int x) {
    
    
        q.push(x);
    }
    
    int pop() {
    
    
        int size = q.size();
        //将前size-1个元素重新插入到队列当中
        for(int i = 0;i<size-1;i++)
        {
    
    
            int front = q.front();
            q.pop();
            q.push(front);
        }
        //此时队头元素就相当于是栈顶元素
        int front = q.front();
        q.pop();
        return front;
    }
    
    int top() {
    
    
        return q.back();
    }
    
    bool empty() {
    
    
        return q.empty();
    }
private:
    queue<int> q;
};

用栈实现队列

https://leetcode-cn.com/problems/implement-queue-using-stacks/

两个栈:

  • pushStack:存放插入数据的栈
  • popStack:存放要弹出数据的栈

插入元素:直接往pushStack插入

子函数Check:检查是否要将pushStack的内容导入到popStack

  • 将pushStack的所有元素插入到popStack ===>元素就变成逆序了
插入顺序:1 2 3 4
pushStack: 1 2 3 4  导入到popStack后:4 3 2 1  符合先进先出

弹出队头元素:先进行检查 =>弹出popStack栈顶元素

获取队头元素:先进行检查 =>返回popStack栈顶元素

class MyQueue {
    
    
public:
    MyQueue() {
    
    

    }
    
    void push(int x) {
    
    
        pushStack.push(x);
    }
    void Check() //检查是否要将push栈的内容导入到pop栈
    {
    
    
        if(popStack.empty())
        {
    
    
            while(!pushStack.empty())
            {
    
    
                popStack.push(pushStack.top());
                pushStack.pop();
            }
        }
    }
    int pop() {
    
    
        Check();
        int top = popStack.top();
        popStack.pop();
        return top;
    }
    
    int peek() {
    
    
        Check();
        return popStack.top();
    }
    
    bool empty() {
    
    
        return pushStack.empty() && popStack.empty();
    }
private:
    stack<int> pushStack;//存放数据的栈
    stack<int> popStack;//用于弹出数据的栈
};

设计循环队列

https://leetcode-cn.com/problems/design-circular-queue/

image-20230821142847657

方式1:使用数组实现

分析:

1.存储k个元素,需要开辟k+1个空间!否则不能准确的判空和判满

2.需要使用两个变量来标志队头和队尾位置,记为fronttail,还需要一个变量size:记录队列当中存储的元素个数

enQueue: 向循环队列插入一个元素

  • 如果队列为满了,直接插入失败
  • 在tail位置插入元素,然后tail++往后走,注意:需要和size+1取模,防止tail越界

deQueue:从循环队列删除元素

  • 如果队列为空,删除失败
  • 只需要front往后走,front++即可,注意:需要和size+1取模,防止越界

获取队头元素:front指向的就是队头元素

获取队尾元素:注意:tail的前一个位置就是队尾元素!但是不能直接返回:arr[tail-1],因为有可能上一次放的位置是size位置,然后tail++取模后到了0位置,所以要判断一下

  • 如果tail不是0位置:返回arr[tail-1]
  • 如果tail是0位置:返回arr[size]

判断队列是否为满:(tail + 1) % (size+1) == front;

class MyCircularQueue {
    
    
public:
    MyCircularQueue(int k) {
    
    
        arr.resize(k+1);//开辟k+1个空间
        front = tail = 0;
        size = k;
    }
    
    bool enQueue(int value) {
    
      //向循环队列插入一个元素。如果成功插入则返回真
        if(isFull()) return false;
        arr[tail] = value;
        tail ++;
        tail %= size+1; //tail = tail % (size+1)
        return true;
    }
    
    bool deQueue() {
    
     //从循环队列中删除一个元素。如果成功删除则返回真。
        if(isEmpty()) return false;
        front++;
        front %= size+1;
        return true;
    }
    
    int Front() {
    
    
        if(isEmpty()) return -1;
        return arr[front];
    }
    
    int Rear() {
    
    
        if(isEmpty()) return -1;
        //由于插入元素之后,tail往后走,所以tail的前一个元素才是队尾元素
        if(tail == 0) return arr[size];
        else return arr[tail-1];
    }
    
    bool isEmpty() {
    
    
        return front == tail;
    }
    
    bool isFull() {
    
    
        return (tail + 1) % (size+1) == front;
    }
private:
    vector<int> arr;
    int front;//标志队头
    int tail;//标志队尾
    int size;//实际存储的元素个数
};

方法2:链表实现

存储k个元素,需要构建k+1个节点的循环链表,还需要定义两个指针front和tail标志队头和队尾,最初链表为空二者都指向链表的头

向循环队列插入一个元素:队列为满,插入失败。否则插入到tail节点位置,然后tail节点往后走

从循环队列删除一个元素:队列为空,删除失败。否则front节点往后走

获取队头元素:返回front指针指向的节点的存放的值

获取队尾元素:注意:tail节点的前一个节点才是队尾节点,所以需要从front位置开始往后遍历找到tail的前一个节点,然后返回对应的值

判空:front == tail

判满:tail->next == front

class MyCircularQueue {
    
    
public:
    MyCircularQueue(int k) {
    
    
        //构建k+1个节点的循环链表
        tail = head = new ListNode;
        for(int i = 0;i<k;i++) 
        {
    
    
            ListNode* newnode = new ListNode;
            tail->next = newnode;
            tail = tail->next;
        }
        //tail指向尾节点,让首尾相连
        tail->next = head;

        //注意:要让tail回到起始位置!!!!!因为一开始链表没有有效元素
        head = tail;
    }
    
    bool enQueue(int value) {
    
     //向循环队列插入一个元素。如果成功插入则返回真。
        if(isFull()) return false;
        tail->val = value;
        tail = tail->next;
        return true;
    }
    
    bool deQueue() {
    
    
        if(isEmpty()) return false;
        head = head->next;
        return true;
    }
    
    int Front() {
    
    
        if(isEmpty()) return -1;
        return head->val;
    }
    
    int Rear() {
    
    
        if(isEmpty()) return -1;
        //从head位置遍历查找,tail的前一个节点放的就算队尾元素
        ListNode* cur = head;
        while(cur->next != tail)
            cur = cur->next;
        return cur->val;
    }
    
    bool isEmpty() {
    
    
        return head == tail;
    }
    
    bool isFull() {
    
    
        return tail->next == head;
    }
private:
    ListNode* head;
    ListNode* tail;
};s

最小栈

https://leetcode.cn/problems/min-stack/

image-20230821144641162

方法1:使用两个栈,一个数据栈,一个辅助栈

  • 1.如果最小栈为空,就存放当前入栈数据
  • 2.如果最小栈不为空,

case1:如果此时最小栈的栈顶元素>此时入数据栈的当前数 -> 最小栈中压入当前数
case2:如果此时最小栈的栈顶元素<=此时入数据栈的当前数 -> 最小栈中重复压入此时最小栈的栈顶元素

最小栈和辅助栈,同步压入元素,同步弹出元素,每时每刻得到当前数据栈中数据的最小值 ,二者一一对应

class MinStack {
    
    
public:
    MinStack() {
    
    

    }
    
    void push(int val) {
    
    
        dataStack.push(val);
        //判断要往辅助栈放重复元素还是更小的元素
        if( minStack.empty() || minStack.top() >= val)
            minStack.push(val);
        else 
            minStack.push(minStack.top());//重复压入当前最小栈栈顶元素
    }
    
    void pop() {
    
    
        int top = dataStack.top();
        dataStack.pop();
        minStack.pop(); //坑点!!此时minStack也要同步pop数据
    }
    
    int top() {
    
    
        return dataStack.top();
    }
    
    int getMin() {
    
    
        return minStack.top();
    }
private:
    stack<int> minStack;//辅助栈->存放当前栈中对应的最小值
    stack<int> dataStack;
};

优化:

  • 1.如果最小栈为空,就存放当前入栈数据
  • 2.如果最小栈不为空

case1:如果此时最小栈的栈顶元素**>=**此时入数据栈的当前数 -> 最小栈中压入当前数 ,等于的时候也要插入!!!

case2:如果此时最小栈的栈顶元素<此时入数据栈的当前数 -> 不压入数据

pop数据时:如果此时数据栈的数据和最小栈的栈顶数据一样->最小栈出栈顶元素 否则最小栈不出数据

class MinStack {
    
    
public:
    MinStack() {
    
    

    }
    
    void push(int val) {
    
    
        dataStack.push(val);
        if( minStack.empty() || minStack.top() >= val)
            minStack.push(val);
    }
    
    void pop() {
    
    
        int top = dataStack.top();
        dataStack.pop();
        if(top == minStack.top())
            minStack.pop(); 
    }
    
    int top() {
    
    
        return dataStack.top();
    }
    
    int getMin() {
    
    
        return minStack.top();
    }
private:
    stack<int> minStack;//辅助栈->存放当前栈中对应的最小值
    stack<int> dataStack;
};

栈的压入&弹出序列

https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking

image-20230821145131734

思想就是:使用栈的入栈顺序,模拟这个出栈顺序,如果能模拟出来,就说明是合法的,否则就是非法的!

做法:

使用一个栈,然后遍历pushV数组,模拟入栈顺序,还需要定义一个变量popIndex表示出栈数组遍历到了哪个位置,对于每一次遍历:

  • 先将当前pushV数组的元素进栈
  • 如果栈不为空,并且当前出栈元素 = 栈顶元素,那么就弹出栈顶元素 ==> while循环操作

最后:如果popIndex遍历完了popV数组,说明是可以按这个顺序弹出的,返回true

class Solution {
    
    
public:
    //pushV:元素的入栈顺序  popV:判断该序列是否可能为该栈的弹出顺序
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
    
    
        if(pushV.size() != popV.size()) return false;
        stack<int> st;//模拟进栈顺序
        int popIndex = 0;//当前出栈元素位置
        for(int i = 0;i<pushV.size();i++)
        {
    
    
            //当前元素进栈
            st.push(pushV[i]);
            //如果栈不为空,并且当前出栈元素 = 栈顶元素,那么就弹出
            while(!st.empty() && st.top() == popV[popIndex])
            {
    
    
                popIndex++;
                st.pop();
            }
        }
        //最后:如果popIndex遍历完了popV数组,说明是可以按这个顺序弹出的
        return popIndex == popV.size();
    }
};

逆波兰表达式(后缀表达式)

https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/

image-20230821151608165

思路:遍历字符串

  • 如果是操作数:进栈
  • 如果是操作符:取栈顶的两个元素出来进行运算,然后再把运算结果放到栈中
  • 最后遍历完成后,栈顶元素就是计算结果

注意:栈是后进先出的,所以先拿到的是右操作数,然后才是左操作数

最好是使用switch-case语句,因为有+-*/四种情况,后续如果还有其它情况,也比较容易增加。如果使用if-else,看起来不美观

  • 使用第一个字符判断是操作符还是操作数的时候,要特别小心-,因为操作数可能为负数,所以要特判:如果字符串长度为1:说明就是操作符,否则就是操作数
  • 或者说:取字符串的最后一个字符判断是操作数还是操作符 switch(str.back())

stoi函数:用于将string字符串转为整数

class Solution {
    
    
public:
    int evalRPN(vector<string>& tokens) {
    
    
        stack<int> st;
        //遍历容器,如果是操作数就进栈
        int left = 0,right = 0;
        auto GetNum = [&]()   //获取两个操作数
        {
    
    
            right = st.top(); //注意:先拿到的是右操作数
            st.pop();
            left = st.top();
            st.pop();
        };
        for(auto& str:tokens) //str是string
        {
    
    
            //如果是操作数就进栈,如果是操作符就把当前栈中两个元素拿出来进行运算,然后把运算结果进栈
            switch(str[0])
            {
    
    
                case '+':
                    GetNum();
                    st.push(left + right);
                    break;
                case '-': //坑点:有可能不是操作符,而是操作数->负数
                    if(str.size() == 1) //操作符
                    {
    
    
                        GetNum();
                        st.push(left - right);
                    } 
                    else  //操作数
                    {
    
    
                        st.push(stoi(str));
                    }
                    break;
                case '*':
                    GetNum();
                    st.push(left * right);
                    break;
                case '/':
                    GetNum();
                    st.push(left / right);
                    break;
                default: //操作数
                    st.push(stoi(str));
                    break;
            }
        }
        return st.top();
    }
};

猜你喜欢

转载自blog.csdn.net/chuxinchangcun/article/details/132369246
今日推荐