C++STL之stack和queue容器适配器: 相关习题解析

1. 最小栈

. - 力扣(LeetCode)

思路:

一、整体架构

 
  1. 使用两个栈 stmin_st 分别实现不同的功能。
    • st 用于存放插入的数据,即主要的数据存储栈,模拟常规的数据存储结构。
    • min_st 用于存放最小的元素,通过特定的插入和弹出规则,始终保持栈顶为当前 st 中的最小元素。
 

二、插入操作(push)

 
  1. 对于 st
    • 正常插入新元素,无论该元素的大小如何,都直接将其压入 st。这保证了数据的完整存储,不进行任何筛选或特殊处理。
  2. 对于 min_st
    • min_st 第一次为空时,先插入一个元素。这是初始化操作,确保 min_st 有一个起始元素,以便后续进行比较和更新。
    • 如果插入的元素比 min_st 的栈顶元素小,就将该元素插入 min_st。这样做的目的是当有新的更小元素出现时,更新 min_st 的栈顶,使其始终保持当前 st 中的最小元素在栈顶。例如,如果当前 min_st 的栈顶是 5,新插入的元素是 4,那么将 4 压入 min_st,此时 min_st 的栈顶变为 4,代表当前最小元素为 4。
 

三、弹出操作(pop)

 
  1. 对于 st
    • 正常弹出栈顶元素。按照栈的先进后出原则,删除最后插入 st 的元素。
  2. 对于 min_st
    • 在弹出 st 的元素后,需要检查该元素是否与 min_st 的栈顶元素相等。如果相等,说明当前要弹出的元素是最小元素,那么也需要将 min_st 的栈顶元素弹出。这是为了保持两个栈的同步,确保 min_st 始终反映 st 中的最小元素。例如,如果 stmin_st 的栈顶都是 4,当从 st 中弹出 4 时,也需要从 min_st 中弹出 4,以保证 min_st 的栈顶仍然是 st 中剩余元素的最小元素。
class MinStack {
public:
    stack<int> st;
    stack<int> min_st;
    MinStack() 
    { 
    }
    void push(int val) 
    {
        st.push(val);
        if(min_st.empty() || val <= min_st.top()) 
        {
            min_st.push(val);
        }
    }
    
    void pop() 
    {
        if(st.top() == min_st.top()) 
        {
            min_st.pop();
        }
        st.pop();
    }
    
    int top() 
    {
        return st.top();
    }
    
    int getMin() 
    {
        return min_st.top();
    }
};

2. 栈的弹出压入序列

栈的压入、弹出序列__牛客网

思路:

       首先遍历压入顺序的序列。在遍历过程中,将压入顺序中的元素依次压入辅助栈,同时不断检查辅助栈的栈顶元素是否与弹出顺序的当前元素相等。如果相等,就将辅助栈的栈顶元素弹出,并移动弹出顺序的索引。继续这个过程,直到压入顺序的所有元素都被处理完。最终,如果辅助栈为空,说明给定的压入顺序和弹出顺序是合法的栈的操作顺序;如果辅助栈不为空,则说明不是合法的弹出顺序。

class Solution {
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        int pushi = 0;
        int popi = 0;
        stack<int> st;
        while (pushi < pushV.size()) 
        {
            st.push(pushV[pushi]);
            ++pushi;  
            while (!st.empty() && st.top() == popV[popi]) 
            {
                st.pop();
                ++popi;
            }
        }
        return st.empty();
    }
};

3. 逆波兰表达式求值

. - 力扣(LeetCode)

思路:

一、定义一个栈

 
  1. 使用一个栈来存储操作数。逆波兰表达式(后缀表达式)的特点是不需要考虑运算符的优先级,按照从左到右的顺序进行计算。栈这种数据结构非常适合这种计算方式,因为它可以方便地进行后进先出的操作。
 

二、遍历表达式

 
  1. 逐个字符地遍历逆波兰表达式。
    • 如果遇到数字字符,将其转换为数字并压入栈中。这是因为数字是操作数,在计算过程中需要先存储起来,等待运算符出现时进行计算。
    • 如果遇到运算符,说明需要进行计算。从栈中弹出两个操作数,注意弹出的顺序是先弹出的作为运算符的右操作数,后弹出的作为运算符的左操作数。这是因为栈是后进先出的数据结构,与通常的数学运算顺序相反。
 

三、进行计算

 
  1. 根据遇到的运算符进行相应的计算。
    • 例如,如果遇到加号(+),则将弹出的两个操作数相加;如果遇到减号(-),则进行减法运算;遇到乘号(*)进行乘法运算;遇到除号(/)进行除法运算。
    • 注意在进行除法运算时,需要考虑除数不能为零的情况,以避免出现错误。
 

四、结果处理

 
  1. 将计算得到的结果压入栈中。

        这样,栈中始终存储着尚未处理的操作数和中间结果。继续遍历表达式,重复上述步     骤,直到表达式遍历完毕。

 

五、最终结果

 
  1. 当表达式遍历结束后,栈中应该只剩下一个元素,这个元素就是逆波兰表达式的求值结果。
 

例如,对于逆波兰表达式 “3 4 + 5 *”,具体的计算过程如下:

 
  1. 首先遇到数字 3,压入栈中。此时栈为 [3]。
  2. 遇到数字 4,压入栈中。此时栈为 [3, 4]。
  3. 遇到加号(+),从栈中弹出两个数字 3 和 4,进行加法运算,得到结果 7。将 7 压入栈中。此时栈为 [7]。
  4. 遇到数字 5,压入栈中。此时栈为 [7, 5]。
  5. 遇到乘号(*),从栈中弹出两个数字 7 和 5,进行乘法运算,得到结果 35。将 35 压入栈中。此时栈为 [35]。
 

表达式遍历完毕,栈中只剩下一个元素 35,这就是逆波兰表达式的求值结果。

class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        for(auto& str : tokens) 
        {
            if(str == "+" || str == "-" || str == "*" || str == "/") 
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                switch(str[0])
                {
                    case '+':
                        st.push(left + right);
                        break;
                    case '-':
                        st.push(left - right);
                        break;
                    case '*':
                        st.push(left * right);
                        break;
                    case '/':
                        st.push(left / right);
                        break;
                }
            }
            else 
            {
                st.push(stoi(str));  // stoi 把字符类型转整型 
            }
            
        }
        return st.top();
    }
};

4. 用栈实现队列

. - 力扣(LeetCode)

思路:

一、使用两个栈

 
  1. 使用两个栈,一个用于入队操作(命名为 _pushST),另一个用于出队操作(命名为 _popST)。
 

二、入队操作(push)

 
  1. 当进行入队操作时,将元素直接压入 _pushST。
    • 由于栈是后进先出的数据结构,入队操作相当于在栈顶添加元素,这是很自然的操作。
 

三、出队操作(pop)

 
  1. 首先检查 _popST 是否为空。
    • 如果 _popST 不为空,说明之前已经有一些元素从 _pushST 转移到了 _popST,此时可以直接从 _popST 的栈顶弹出元素,这就实现了队列的先进先出特性。
    • 如果 _popST 为空,将 _pushST 中的所有元素依次弹出并压入 _popST。这样就将 _pushST 中后进先出的顺序反转成了先进先出的顺序,然后再从 _popST 的栈顶弹出元素。
 

四、查看队首元素(peek)

 
  1. 与出队操作类似,先检查 _popST 是否为空。
    • 如果 _popST 不为空,直接返回 _popST 的栈顶元素,这就是队首元素。
    • 如果 _popST 为空,将 _pushST 中的所有元素依次弹出并压入 _popST,然后返回 _popST 的栈顶元素。
 

五、判断队列是否为空(empty)

 
  1. 当 _pushST 和 _popST 都为空时,队列被认为是空的。
  2. 因为只有这两个栈都没有元素时,才能确定整个队列中没有任何元素。
class MyQueue 
{

private:
    stack<int> _PushST;
    stack<int> _PopST;

public:
    MyQueue() {}

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

    int pop() 
    {
        int front = peek();  //返回队列的 前一个位置
        _PopST.pop();
        return front;
    }

    int peek() 
    {
        if (!_PopST.empty()) 
        {
            return _PopST.top();
        } 
        else 
        {
            while (!_PushST.empty()) 
            {
                _PopST.push(_PushST.top());
                _PushST.pop();
            }
            return _PopST.top();
        }
    }

    bool empty() 
    { 
        return _PushST.empty() && _PopST.empty(); 
    }
};

5. 用队列实现栈

. - 力扣(LeetCode)

一、利用两个队列

 
  1. 有两个队列,分别命名为 q1q2
 

二、入栈操作(push)

 
  1. 检查哪个队列非空。
    • 如果 q1 非空,就将新元素加入 q1
    • 如果 q2 非空,就将新元素加入 q2
    • 如果两个队列都为空,可以任意选择一个队列加入新元素。
 

三、出栈操作(pop)

 
  1. 确定哪个队列为非空,哪个队列为空。假设 q1 非空,q2 为空。
  2. 将非空队列(这里是 q1)中的元素逐个出队并加入空队列(q2),除了最后一个元素。这样做是为了将除了最后进入的元素(即栈顶元素)之外的所有元素转移到另一个队列中。
  3. 最后,将非空队列中剩下的那个元素出队并返回,这个元素就是栈顶元素。
 

四、查看栈顶元素(top)

 
  1. 与出栈操作类似,确定非空队列和空队列。
  2. 将非空队列中的元素逐个出队并加入空队列,除了最后一个元素。
  3. 记录这个最后一个元素的值,但不要出队。
  4. 再将这些元素逐个从空队列出队并加入原来的非空队列,恢复队列的状态。
  5. 返回之前记录的元素值,即栈顶元素。
  6. 但是这里可以直接使用 back。

class MyStack {
public:
    queue<int> q1;
    queue<int> q2;

    MyStack() {}

    void push(int x) 
    {
        if (!q1.empty())
            q1.push(x);
        else 
            q2.push(x);
    }

    int pop() {
        queue<int>* empty = &q1;
        queue<int>* noempty = &q2;
        if (!q1.empty()) {
            noempty = &q1;
            empty = &q2;
        }
        while (noempty->size() > 1) 
        {
            empty->push(noempty->front());
            noempty->pop();
        }
        int top = noempty->front();
        noempty->pop();
        return top;
    }

    int top() 
    {
        if (!q1.empty()) 
            return q1.back();
        else 
            return q2.back();
    }

    bool empty() 
    {
        return q1.empty() && q2.empty();
    }
};

6. 数组中第K个大的元素

. - 力扣(LeetCode)

方法一:优先级队列求解

     把数组的数据入队,然后pop前 k-1 个数据,栈顶就是第 K 大的数。

时间复杂度:堆排序的时间复杂度是 N*logN,取最坏,时间复杂度是 N*logN。

空间复杂:构建了堆,空间复杂度是O(N)。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        priority_queue<int>   pq;
        for(auto& e : nums) 
        {
            pq.push(e);
        }
        while(--k) 
        {
            pq.pop();
        } 
        return pq.top();     
    }
};

方法二:排序

     排序的底层是快排,快排使用三数取中法,避免了最坏的情况。

时间复杂度是 O(N*logN)

空间复杂度是 O(1)

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        sort(nums.begin(), nums.end());
        return nums[nums.size() - k];
    }
};

方法三:topK问题。

      当要求前第 K 个最大的数的时候,建立小堆,当要求前第 K 个最小的数的时候,建立大堆,这是二叉树部分的堆的基础。 我们构建一个小堆,小堆是 K个数据,时间复杂度是 K+logK,即 logK, 第一个for循环时间复杂度是 K*logK,第二个for循环时间复杂度是 N*logK,需要调整 K 个数据的大小 。

所以时间复杂度: O(K*logK) 空间复杂度:O(K)

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        priority_queue<int, vector<int>, greater<int>> minHeap;
        // 1,建立 K个数的小堆
        size_t i = 0;
        for (; i < k; ++i) 
        {
            minHeap.push(nums[i]);
        }
        for (; i < nums.size(); ++i) 
        {
            if (nums[i] > minHeap.top()) 
            {
                minHeap.pop();
                minHeap.push(nums[i]);
            }
        }
        return minHeap.top();
    }
};

7. 有效的括号

. - 力扣(LeetCode)

思路:

一、整体方法

 

这个思路是通过使用栈来判断给定字符串中的括号是否匹配正确。对于包含不同类型括号的字符串,如小括号 ()、中括号 []、大括号 {} 等,该方法可以有效地检查它们的匹配情况。

 

二、三 种情况分析

 
  1. 情况①()

    • 当遇到左括号 ( 时,将其入栈。
    • 后续如果遇到右括号 ),此时栈顶元素应为 (,如果匹配成功则弹出栈顶元素。如果整个字符串遍历完后栈为空,则说明括号完全匹配。
  2. 情况②( [ { } ] )

    • 同样,遇到左括号时入栈。例如遇到 [,将其入栈。
    • 当遇到右括号 ] 时,检查栈顶元素是否为对应的左括号 [。如果是,则弹出栈顶元素;如果不是,则说明括号不匹配,直接返回 false
  3. 情况③( )[ ] { }

    • 对于这种混合括号的情况,也是遵循相同的逻辑。遇到左括号入栈,遇到右括号时与栈顶元素进行匹配。如果匹配成功则弹出栈顶元素,否则说明括号不匹配。
 

三、栈的作用

 
  1. 记录左括号:栈用于存储遍历过程中遇到的左括号。这样,当遇到右括号时,可以方便地与栈顶的左括号进行匹配。
  2. 确保顺序:通过入栈和出栈的操作,可以确保括号的匹配是按照正确的顺序进行的。只有当右括号与最近的未匹配的左括号相匹配时,才是正确的情况。
class Solution {
public:
    stack<char> st;
    bool isValid(string s) 
    {
        for(int i = 0; i < s.size(); ++i) 
        {
            if(s[i] == '(' || s[i] == '{' || s[i] == '[') 
            {
                st.push(s[i]);
            }
            else
            {
                if(st.empty()) 
                {
                    return false;
                }
                char top = st.top();
                if(s[i] == ')' && top != '(') 
                {
                    return false;
                }
                if(s[i] == ']' && top != '[') 
                {
                    return false;
                }
                if(s[i] == '}' && top != '{') 
                {
                    return false;
                }
                st.pop();
            }
        }
        return st.empty();
    }
};

8. 字符串解码

. - 力扣(LeetCode)

9. 每日温度

. - 力扣(LeetCode)

思路:

① 一种很简单的做法就是暴力求解,两层for循环,一层遍历数组,一层找到比当前元素更大的元素在哪里,两个下标相减,求到对应的值,时间复杂度是 O(n²)。不太行。

②利用单调栈求解,时间复杂度是 O(n)。

   单调栈:可以完成找到左边或者右边比它大或者比它小的元素。

   什么是单调栈:

  • 单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
  • 单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大

那么这里求元素之间的距离,如果存放元素到栈里面,还要去数组中找元素对应的下标是多少,而且数组元素还会存在重复的,无法确定唯一下标,所以单调栈直接存放元素的下标,然后进行比较大小(数组通过下标映射元素进行比较)。

单调栈的作用?

存放遍历过的元素,我们怎么知道这个元素是不是第一个比遍历过的元素大的元素呢?我们需要一个数据结构记录我们之前遍历过的元素。

例如:

[73,74,75,71,69,69,72,76,73]。

76是第一个比75大的元素,然后用下标相减求差值,计算的 5。

综上所述:单调栈的作用是记录一下我们遍历过的元素,然后和当前遍历的元素做一个对比。

求解过程:

当前遍历元素和栈顶元素作比较,有三种情况,要么比当前元素大,要么小,要么等于。

根据这些情况,我们就可以来模拟单调栈的过程。

  1. 初始化:

    • 创建一个空栈st
    • 创建结果数组result,初始值全为 0,长度与输入数组相同。
    • 将索引 0(对应元素 73)入栈,即st.push(0)
  2. 处理元素 74:

    • i = 1,当前元素为 74。
    • 74 大于栈顶元素 73(T[i] > T[st.top()])。
    • 执行while循环:
      • result[st.top()] = i - st.top(),即result[0] = 1 - 0 = 1,表示第一个元素 73 升温天数为 1。
      • st.pop(),弹出栈顶元素。
    • 将索引 1(对应元素 74)入栈,即st.push(1)
  3. 处理元素 75:

    • i = 2,当前元素为 75。
    • 75 大于栈顶元素 74(T[i] > T[st.top()])。
    • 执行while循环:
      • result[st.top()] = i - st.top(),即result[1] = 2 - 1 = 1,表示第二个元素 74 升温天数为 1。
      • st.pop(),弹出栈顶元素。
    • 将索引 2(对应元素 75)入栈,即st.push(2)
  4. 处理元素 71:

    • i = 3,当前元素为 71。
    • 71 小于栈顶元素 75(T[i] <= T[st.top()])。
    • 将索引 3(对应元素 71)入栈,即st.push(3)
  5. 处理元素 69:

    • i = 4,当前元素为 69。
    • 69 小于栈顶元素 71(T[i] <= T[st.top()])。
    • 将索引 4(对应元素 69)入栈,即st.push(4)
  6. 处理元素 69(重复的 69):

    • i = 5,当前元素为 69。
    • 69 小于栈顶元素 69(栈顶元素未变,还是对应索引 4),即T[i] <= T[st.top()]
    • 将索引 5(对应元素 69)入栈,即st.push(5)
  7. 处理元素 72:

    • i = 6,当前元素为 72。
    • 72 大于栈顶元素 69(T[i] > T[st.top()])。
    • 执行while循环:
      • result[st.top()] = i - st.top(),即result[5] = 6 - 5 = 1,表示第六个元素 69 升温天数为 1。
      • st.pop(),弹出栈顶元素。
      • 栈顶元素变为对应索引 4 的 69,72 仍大于栈顶元素。
      • result[st.top()] = i - st.top(),即result[4] = 6 - 4 = 2,表示第五个元素 69 升温天数为 2。
      • st.pop(),弹出栈顶元素。
      • 栈顶元素变为对应索引 3 的 71,72 仍大于栈顶元素。
      • result[st.top()] = i - st.top(),即result[3] = 6 - 3 = 3,表示第四个元素 71 升温天数为 3。
      • st.pop(),弹出栈顶元素。
    • 将索引 6(对应元素 72)入栈,即st.push(6)
  8. 处理元素 76:

    • i = 7,当前元素为 76。
    • 76 大于栈顶元素 72(T[i] > T[st.top()])。
    • 执行while循环:
      • result[st.top()] = i - st.top(),即result[6] = 7 - 6 = 1,表示第七个元素 72 升温天数为 1。
      • st.pop(),弹出栈顶元素。
      • 栈顶元素变为对应索引 2 的 75,76 仍大于栈顶元素。
      • result[st.top()] = i - st.top(),即result[2] = 7 - 2 = 5,表示第三个个元素 75 升温天数为 5。
      • st.pop(),弹出栈顶元素。
    • 将索引 7(对应元素 76)入栈,即st.push(7)
  9. 处理元素 73:

    • i = 8,当前元素为 73。
    • 73 小于栈顶元素 76(T[i] <= T[st.top()])。
    • 将索引 8(对应元素 73)入栈,即st.push(8)
 

最终,结果数组result[1, 1, 5, 3, 2, 1, 1, 0, 0],表示每个元素距离下一个更高温度的天数。

 

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) 
    {
        stack<int> st;
        vector<int> result(T.size(), 0);
        st.push(0);
        for(int i = 1; i < T.size(); ++i) 
        {
            if(T[i] > T[st.top()]) 
            {
                while(!st.empty() && T[i] > T[st.top()])
                {
                    result[st.top()] = i - st.top();
                    st.pop();
                }
                st.push(i);
            }
            else
            {
                st.push(i);
            }
           
        }  
        return result;
    }
};

10. 接雨水

. - 力扣(LeetCode)

11. 柱状图中最大的矩形

. - 力扣(LeetCode)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值