30.包括min函数的栈

核心在于用另一个栈来保存当前栈的最小值,并且另一个栈顶始终是最小值,顺序按照原始栈的顺序来。

  1. class MinStack {
  2. public:
  3. stack<int> stack1, stack2;
  4. /** initialize your data structure here. */
  5. MinStack() {
  6. while(!stack1.empty()){
  7. stack1.pop();
  8. }
  9. while(!stack2.empty()){
  10. stack2.pop();
  11. }
  12. }
  13. void push(int x) {
  14. if(stack2.empty()||stack2.top() >= x){
  15. stack2.push(x);
  16. }
  17. stack1.push(x);
  18. }
  19. void pop() {
  20. if(stack1.top() == stack2.top()){
  21. stack2.pop();
  22. }
  23. stack1.pop();
  24. }
  25. int top() {
  26. return stack1.top();
  27. }
  28. int min() {
  29. return stack2.top();
  30. }
  31. };
  32. /**
  33. * Your MinStack object will be instantiated and called as such:
  34. * MinStack* obj = new MinStack();
  35. * obj->push(x);
  36. * obj->pop();
  37. * int param_3 = obj->top();
  38. * int param_4 = obj->min();
  39. */

09.用两个栈实现队列

核心在于出现delete操作时就将两个栈倒装一次。保持队列的形式,只有当用于倒装的栈为空时,出现delete操作就会再次倒装。

class CQueue {
    stack<int> stack1,stack2;
public:
    CQueue() {
        while(!stack1.empty()){
            stack1.pop();
        } 
        while(!stack2.empty()){
            stack2.pop();
        }
    }//这个构造函数的作用就是清空两个栈

    void appendTail(int value) {
        stack1.push(value);
    }

    int deleteHead() {
        // 如果第二个栈为空
        if (stack2.empty()) {
            while (!stack1.empty()) {
                stack2.push(stack1.top());
                stack1.pop();
            }
        } 
        if (stack2.empty()) {
            return -1;
        } else {
            int deleteItem = stack2.top();
            stack2.pop();
            return deleteItem;
        }
    }
};

剑指 Offer II 036. 后缀表达式(逆波兰式)

意思就是把运算符放在后面

  • 使用栈来实现
  • switch不能判断字符串
    class Solution {
    public:
      int evalRPN(vector<string>& tokens) {
          stack<int> stk;
          for(int i = 0; i < tokens.size(); i++){
              string a = tokens[i];
              if(isdigit(a[0])||isdigit(a[1])){
                  stk.push(stoi(tokens[i]));
              } else {
                  int num1 = stk.top();
                  stk.pop();
                  int num2 = stk.top();
                  stk.pop();
                  switch (a[0]){
                      case '*': num1 = num1*num2;break;
                      case '+': num1 = num1+num2;break;
                      case '-': num1 = num2-num1;break;
                      case '/': num1 = num2/num1;break;
                  }
                  stk.push(num1);
              }
          }
          return stk.top();
      }
    };
    

剑指 Offer II 037. 小行星碰撞

就是情况有点多。

class Solution {
public:
    vector<int> asteroidCollision(vector<int>& asteroids) {
        stack<int> stk;
        for(int i = 0; i < asteroids.size(); i++){
            if(stk.empty()||asteroids[i]>0) {
                stk.push(asteroids[i]);
            } else {
                while(!stk.empty()&&stk.top() > 0&&abs(stk.top())<abs(asteroids[i])){
                    stk.pop();
                }
                if(stk.empty()||stk.top()<0) {
                    stk.push(asteroids[i]);
                } else if(abs(stk.top()) == abs(asteroids[i])) stk.pop();
            }
        }
        vector<int> res;
        while(!stk.empty()) {
            res.push_back(stk.top());
            stk.pop();
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

单调栈

剑指 Offer II 038. 每日温度(单调栈)

可以维护一个存储下标的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减。如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        stack<int> stk;
        vector<int> res(n);
        for(int i = 0; i < n; i++){
            while(!stk.empty()){
                int pre = stk.top();
                if(temperatures[pre]>=temperatures[i]){
                    break;
                }
                res[pre] = i - pre;
                stk.pop();
            }
            stk.push(i);
        }
        return res;
    }
};

剑指 Offer II 039. 直方图最大矩形面积(hard)

  • 注意单调栈存储的是下标
  • 每个栈顶下标都代表这当前的高度
  • 每当一个元素高度小于当前栈顶就代表找到了栈顶高度的右测,而因为栈是从小到大单调的,因此左侧也比较容易找到,就是当前栈顶的前一个元素(也就是当前栈顶pop之后的新的栈顶)
  • 遇到比栈顶元素小的元素,不是算以当前元素为顶的矩形面积,而是算以栈顶元素为顶的矩形面积。有点反直觉。
  • 归纳一下枚举高的方法:

    • 首先我们枚举某一根柱子i作为高h
    • 随后我们需要向两边扩展,使得扩展的柱子高度均不小于h。换句话说我们需要找到找到左右两侧最近的高度小于h的柱子。
    • 但是如果有两根柱子0和1,其中0在1的左侧,并且0的高度大于等于1,那么在后面的柱子i向左找小于其高度的柱子时,1会挡住0,0就不会作为答案了。

      class Solution {
      public:
      int largestRectangleArea(vector<int>& heights) {
         stack<int> st;//单调栈是逐渐增大的,后入栈的应该都比前面的高度大。
         //千万注意,单调栈存储的是下标
         st.push(-1);//设置左墙壁
         int n =heights.size();
         int ans =0;
         for(int i=0;i<n;i++){
             while(st.top()!=-1 && heights[st.top()]>=heights[i]){//循环维持栈的单调性
                 //计算过以栈顶元素的高之后,就pop出来
                 int tem = st.top();
                 st.pop();
                 //同11. 盛最多水的容器  一样高度减少  但是宽度增加 还是要比较面积大小
                 int curArea = heights[tem]*(i-st.top()-1);//当-1作为左端时,宽度实际上就是右端的下标
                 ans = max(ans,curArea);                
      
             }
             st.push(i);
         }
         //此时的栈顶高度,索引后面的都比他大,索引前面的在栈中都比他小,因此右端为n,左端为新的栈顶。
         while(st.top()!=-1){
                 int tem = st.top();
                 st.pop();
                 int curArea = heights[tem]*(n-st.top()-1);
                 ans=max(ans,curArea);
         }
         return ans;
      }
      };
      

      剑指 Offer II 040. 矩阵中最大的矩形(重点)

      这题就是上一题的加强版本

  • 需要提前算出的左端最长连续个数

  • 用这个i条件来作为单调栈建立的标准
  • heights表示当前行每个位置从上到下连续为1的次数
  • 后面的内容和上题类似,知道了每一列的连续1的次数,就相当于前面知道了高度。然后求建筑的最大面积。这里注意,行是会一直变化的,也就是说这一题的矩阵是可以不靠着x轴的,对应上一题也就是说建筑是可以悬空的。因此需要枚举每一行

    class Solution {
    public:
      int maximalRectangle(vector<string>& matrix) {
          int n = matrix.size();
          if(n == 0) return 0;
          int ans = 0;
          vector<int> heights(matrix[0].size());
          //这里的这个可以通过类似dp的方法得到
          //这里还进行了状态压缩
          for(string row : matrix){//提前得到左端的最长连续1个数
              for(int i = 0; i < row.size(); i++){
                  heights[i] = row[i] == '0' ? 0 : heights[i] + 1;
              }
              ans = max(ans, maximalRectangle(heights));
          }
          return ans;
      }
    
      int maximalRectangle(vector<int>& heights){
          int n = heights.size();
          int ans = 0;
          stack<int> s;
          s.push(-1);// 防空
          //后面的部分就和直方图最大矩形类似了
          for(int i = 0; i < n; i++){//栈是从小到大,当遇到小于栈顶的情况时,就找到了以当前栈顶为宽度的情况。此时前后都小于它
          //这一部分是每一列的情况
              while(s.top() != -1 && heights[s.top()] >= heights[i]){
                  int tp = s.top();
                  s.pop();
                  ans = max(ans, heights[tp] * (i - s.top() - 1));//下端-上端-1
              }
              s.push(i);
          }
          //栈中剩下来的元素都比后面的小,因此下端默认为n了
          while(s.top() != -1){
              int tp = s.top();
              s.pop();
              ans = max(ans, heights[tp] * (n - s.top() - 1));
          }
          return ans;
      }
    };