C++/JAVA中的栈及常见题目汇总

  目录

一、栈的介绍

  1.1 栈空间特性

  1.2 栈的自实现

    1.2.1 栈的线性存储

    1.2.2 栈的链式存储

   1.3 栈的应用:深度优先算法

二、栈的相关题目

  1. 队列和栈相互实现
    1. 用队列实现栈
    2. 用栈实现队列  
  2. 借助辅助栈实现
    1. 包含min函数的栈
    2. 栈的最小值
  3. 二叉树的几种遍历方式
    1. 二叉树的前序遍历
    2. 二叉树的中序遍历
    3. 二叉树的后序遍历
    4. 二叉树的层序遍历(队列)
    5. 二叉树的之字形遍历  
  4. 匹配括号(与加减乘除括号等特殊字符有关的)号
    1. 有效的括号
    2. 字符串解码
    3. 波兰表达式求值
    4. 棒球比赛
    5. 基本计算器2
    6. 移除无效的括号
    7. 括号的分数
  5. 单调栈
    1. 每日温度  
    2. 下一个更大元素1
    3. 下一个更大元素2
    4. 最短无连续子数组
    5. 接雨水
    6. 股票价格跨度
    7. 柱状图中最大的矩形
    8. 去除重复字母
    9. 不同字符的最小子序列
    10. 移掉k位数字
  6. 滑动窗口的最大值:栈和队列的对比
    1. 滑动窗口的最大值
    2. 滑动窗口的最大值2 
  7. 验证栈序列
  8. 删除最外层的括号
  9. 简化路径
  10. 删除字符串中的所有相邻重复项
    1. 删除字符串中所有相邻重复项
    2. 删除字符串中的所有相邻重复项2  
  11. 用栈构建其他数据结构
    1. 用栈构建数组 
    2. 化队为栈 
  12. 整理字符串
  13. 行星碰撞
  14. 反转每对括号间的子串

一、栈的介绍

1.1 栈空间的特性

  • 栈中存放任意类型的变量,但必须是auto修饰的,即自动类型的局部变量。随用随开,用完即消
  • 内存的分配和销毁是由系统自动完成的,不需要人工干预。
  • 栈内存不可返回,调用函数就是开辟一个栈的空间,随用随开,用完即消

1.2 栈的自实现

  • 栈的特点

  栈的典型特点是先入后出,或者后入先出。只要是接口操作,分别是:判空、判满、压栈、出栈。

  

 

 

 1.2.1 栈的线性存储

  主要分为:栈的初始化、栈的判空、栈的判满、栈的压入和栈的弹出操作

  栈的初始化:由于是线性结构,因此给栈初始化长度为我们所需要的长度

  如果栈顶标号_top==0,则为空,如果栈顶标号_top==_len,则为满

  栈的压入:由于栈顶标号_top指向待操作的元素,因此先插入元素,再将_top增加

  栈的弹出:由于栈顶标号_top指向待插入的元素,因此要删除元素,需要先将_top减小,再实现元素的弹出

 1 typedef struct _stack
 2 {
 3     int _len;
 4     char* _space;
 5     int _top;
 6 }Stack;
 7 
 8 void initStack(Stack* s, int len)
 9 {
10     s->_top = 0;
11     s->_space = new char[sizeof(char)*len];//s->_space=(char*)malloc(sizeof(char)*len)
12     s->_len = len;
13 }
14 
15 bool isStackFull(Stack* s)
16 {
17     return s->_top == s->_len;
18 }
19 
20 bool isStackEmpty(Stack* s)
21 {
22     return s->_top == 0;
23 }
24 
25 void mypush(Stack* s, int ch)
26 {
27     s->_space[s->_top++] = ch;
28 }
29 
30 char mypop(Stack* s)
31 {
32     return s->_space[--s->_top];
33 }

 使用C++类封装后栈的线性存储

mystack.h

 1 #pragma once
 2 class mystack
 3 {
 4 public:
 5     //栈的初始化
 6     void initStack(int len);
 7     //栈的判空
 8     bool isEmpty();
 9     //栈的判满
10     bool isFull();
11     //栈的压入
12     void mypush(char ch);
13     //栈的弹出
14     char mypop();
15 
16 private:
17     int _len;
18     char* _space;
19     int _top;
20 };

mystack.cpp

 1 #include "mystack.h"
 2 
 3 //栈的初始化
 4 void mystack::initStack(int len)
 5 {
 6     _len = len;
 7     _space = new char[len];
 8     _top = 0;
 9 }
10 
11 //栈的判空
12 bool mystack::isEmpty()
13 {
14     return _top == 0;
15 }
16 
17 //栈的判满
18 bool mystack::isFull() {
19     return _top == _len;
20 }
21 
22 //栈的压入
23 void mystack::mypush(char ch) {
24     _space[_top++] = ch;
25 }
26 
27 //栈的弹出
28 char mystack::mypop()
29 {
30     return _space[--_top];
31 }

main

 1 #include <iostream>
 2 #include "mystack.h"
 3 
 4 using namespace std;
 5 
 6 int main() 
 7 {
 8     mystack s;
 9     s.initStack(100);
10     for (int ch = 'a'; ch <= 'z'; ++ch)
11     {
12         s.mypush(ch);
13     }
14     while (!s.isEmpty())
15     {
16         cout << s.mypop() << " ";
17     }
18     cout << endl;
19 }

 

1.2.2 栈的链式存储

  栈的链式存储即栈中每个元素是用链表来实现的,因此设计时选择表头作为栈顶指针,而不是表尾,不同于线式存储,由于是链表,因此其没有判满操作

  注意:栈的链式存储中,_top是位于链表尾部的,由于要满足先入先出原则,使用头插法可以实现,因此申请一个_top为头结点

  由于其是链式存储的,因此是利用链表来实现栈的,链表必然涉及到其头结点,因此要初始化即需要申请一个链表节点_top,_top.next=nullptr

  链式存储是不需要判满的

  链表的判空:当_top的下一个节点为空,其则为空

  链表的压入操作:将节点利用头插法插入链表,让新来的节点有所指向

  链表的删除操作:删除_top的下一个结点

 1 typedef struct _node
 2 {
 3     char data;
 4     struct _node* next;
 5 }Node;
 6 
 7 typedef struct _stacklist
 8 {
 9     Node* _top;
10 }Stacklist;
11 
12 void initStacklist(Stacklist* s)
13 {
14     s->_top = nullptr;
15 }
16 
17 bool isStacklistEmpty(Stacklist* s)
18 {
19     return s->_top == nullptr;
20 }
21 
22 //使用尾插法来实现
23 void stacklistpush(Stacklist* s, char ch)
24 {
25     Node* cur = new Node;
26     cur->data = ch;
27     cur->next = s->_top;
28     s->_top = cur;
29 }
30 
31 char stacklistpop(Stacklist* s)
32 {
33     Node* t = s->_top;
34     char ch = t->data;
35     s->_top = s->_top->next;
36     return ch;
37     delete t;
38 }

 

使用C++封装后的栈的链式存储

mystackList.h

 1 #pragma once
 2 //栈的链式存储
 3 typedef struct _node {
 4     char data;
 5     struct  _node* next;
 6 }Node;
 7 class mystackList
 8 {
 9 public:
10     //链式存储就不需要判满了
11     void initStackList();
12     bool isStackEmpty();
13     void mypush(char ch);
14     char mypop();
15 private:
16     Node* _top;
17 };

mystackList.cpp

 1 #include "mystackList.h"
 2 
 3 //栈的初始化
 4 void mystackList::initStackList()
 5 {
 6     _top = new Node;
 7     _top->next = nullptr;
 8 }
 9 
10 //判断栈是否为空
11 bool mystackList::isStackEmpty() {
12     return _top->next == nullptr;
13 }
14 
15 //入栈,使用尾插法实现
16 void mystackList::mypush(char ch)
17 {
18     Node* cur = new Node;
19     cur->data = ch;
20     cur->next = _top->next;
21     _top->next = cur;
22 }
23 //出栈,相当于链表的出栈
24 char mystackList::mypop()
25 {
26     Node* t = _top->next;
27     char ch = t->data;
28     _top = _top->next;
29     return ch;
30     delete t;
31 }

 

main.cpp

 1 #include <iostream>
 2 #include "mystack.h"
 3 #include "mystackList.h"
 4 
 5 using namespace std;
 6 
 7 int main() 
 8 {
 9     /*mystack s;
10     s.initStack(100);
11     for (int ch = 'a'; ch <= 'z'; ++ch)
12     {
13         s.mypush(ch);
14     }
15     while (!s.isEmpty())
16     {
17         cout << s.mypop() << " ";
18     }
19     cout << endl;*/
20     mystackList s;
21     s.initStackList();
22     for (char ch = 'a'; ch <= 'z'; ++ch)
23     {
24         s.mypush(ch);
25     }
26     while (!s.isStackEmpty())
27         cout << s.mypop() << " ";
28     cout << endl;
29 }

 

 

1.3 栈的应用:深度优先算法

  • 题目描述:打印地图版:从地图的某一点开始,打印出其所能走通的路径

  

 

  •  代码参考

mystack.h

 1 typedef struct _point
 2 {
 3     int _x;
 4     int _y;
 5 }Point;
 6 
 7 typedef struct _node
 8 {
 9     Point data;
10     struct _node* next;
11 }Node;
12 
13 typedef struct _stacklist
14 {
15     Node* _top;
16 }StackList;

 

mystack.cpp

 1 #include <iostream>
 2 #include "mystack.h"
 3 using namespace std;
 4 
 5 
 6 void initStacklist(StackList* s)
 7 {
 8     s->_top = nullptr;
 9 }
10 
11 bool isStacklistEmpty(StackList* s)
12 {
13     return s->_top == nullptr;
14 }
15 
16 void mypush(StackList* s, Point data)
17 {
18     Node* cur = new Node;
19     cur->data = data;
20     cur->next = s->_top;
21     s->_top = cur;
22 }
23 
24 Point mypop(StackList* s)
25 {
26     Node* t = s->_top;
27     Point data = t->data;
28     s->_top = s->_top->next;
29     return data;
30     delete t;
31 }

 

main.cpp

#include <iostream>
#include "mystack.h"
using namespace std;


#define MAXROW 10
#define MAXLINE 10
int maze[MAXROW][MAXLINE] = 
{
    1,1,1,1,1,1,1,1,1,1,
    0,0,0,1,1,1,1,1,1,1, 
    1,1,0,1,1,1,1,1,1,1, 
    1,1,0,0,0,0,1,1,1,1, 
    1,1,0,1,1,0,1,1,1,1,
    1,1,0,1,1,0,1,1,1,1, 
    1,1,1,1,1,0,1,1,1,1, 
    1,1,1,1,1,0,0,0,1,1, 
    1,1,1,1,1,1,1,0,0,0, 
    1,1,1,1,1,1,1,1,1,1,
};

void displyMaze() 
{
    for (int i = 0; i < MAXROW; i++) 
    { 
        for (int j = 0; j < MAXLINE; j++) 
        { 
            if (maze[i][j] == 1)
                printf("%2s", " *");
            else if (maze[i][j] == 2) 
                printf("%2s", " #"); 
            else printf("%2s", " ");
        } 
        putchar(10);
    }
    printf(" ====================\n"); 
}

StackList s;

void visit(int x, int y)
{
    Point p = { x,y };
    mypush(&s, p);
}



int main()
{
    Point sp = { 1,0 };
    Point ep = { 8,9 };
    displyMaze();
    mypush(&s, sp);
    int flag = 0;
    while (!isStacklistEmpty(&s))
    {
        Point t = mypop(&s);
        maze[t._x][t._y] = 2;//走过的路径设置为2
        system("cls");
        displyMaze();
        if (t._y - 1 >= 0 && maze[t._x][t._y - 1] == 0)
            visit(t._x, t._y - 1);
        if (t._y + 1 <= 9 && maze[t._x][t._y + 1] == 0)
            visit(t._x, t._y + 1);
        if (t._x - 1 >= 0 && maze[t._x - 1][t._y] == 0)
            visit(t._x - 1, t._y);
        if (t._x + 1 >= 0 && maze[t._x + 1][t._y] == 0)
            visit(t._x + 1, t._y);
        if (t._x == ep._x && t._y == ep._y)
        {
            flag = 1;

        }
        if (flag == 1)
            cout << "find the path" << endl;
        else
            cout << "find no path";
    }
    return 0;
}

 

二、栈的相关题目

1. 栈和队列相互实现

  1.1 用队列实现栈

  

 

  •  题目分析

  首先区分栈和队列,队列是先入先出,栈是先入后出

   由于队列是先入先出,栈是先入后出,因此要利用队列实现栈,则需要借助辅助队列即可

  对于压栈操作,如果主队列中有元素,可以将主队列qi中的元素以队列的方式放入辅助队列qt,将元素x插入主队列后再将辅助队列中的元素插入到主队列中,这样即可实现先入先出操作

  对于JAVA中的栈常见操作

栈的实例化
Stack stack=new Stack();
泛化性实例化
Stack<Integer> s=new Stack<Integer>();
s.empty()   判读栈是否为空
s.peek()     取栈顶值,不出栈
s.push()     入栈
s.pop()       出栈

 

  对于JAVA中的队列常见的操作

队列的申请:
Queue<Integer> queue=new LinkedList<Integer>();
add             增加一个元素,如果队列已满,则抛出异常
remove        溢出并返回队列头部的元素,如果队列为空,则抛出异常
element       返回队列头部的元素,如果队列为空,则抛出异常
offer            添加一个元素并返回true,如果队列已满,则返回false
poll             返回并移除队列头部的元素,如果队列为空,则返回null
peek           返回队列头部的元素,如果队列为空,则返回null
put             添加一个元素,若队列满,则阻塞
take           移除并返回队列头部的元素,如果队列为空,则阻塞。

 

  •  代码参考
 1 class MyStack {
 2 public:
 3     /** Initialize your data structure here. */
 4     //借助辅助队列将要插入的元素排至队列头
 5     queue<int> qi;
 6     queue<int> qt;
 7     MyStack() {
 8 
 9     }
10     
11     /** Push element x onto stack. */
12     void push(int x) {
13         while(!qi.empty())
14         {
15             qt.push(qi.front());
16             qi.pop();
17         }
18         qi.push(x);
19         while(!qt.empty())
20         {
21             qi.push(qt.front());
22             qt.pop();
23         }
24             
25     }
26     
27     /** Removes the element on top of the stack and returns that element. */
28     int pop() {
29         int temp=qi.front();
30         qi.pop();
31         return temp;
32     }
33     
34     /** Get the top element. */
35     int top() {
36         return qi.front();
37     }
38     
39     /** Returns whether the stack is empty. */
40     bool empty() {
41         return qi.empty();
42     }
43 };
44 
45 /**
46  * Your MyStack object will be instantiated and called as such:
47  * MyStack* obj = new MyStack();
48  * obj->push(x);
49  * int param_2 = obj->pop();
50  * int param_3 = obj->top();
51  * bool param_4 = obj->empty();
52  */
  •  JAVA代码参考
 1 class MyStack {
 2 
 3     Queue<Integer> qi;
 4     Queue<Integer> qt;
 5     /** Initialize your data structure here. */
 6     public MyStack() {
 7         qi=new LinkedList<Integer>();
 8         qt=new LinkedList<Integer>();
 9     }
10    
11 
12     /** Push element x onto stack. */
13     public void push(int x) {
14         while(!qi.isEmpty())
15         {
16             qt.offer(qi.poll());
17         }
18         qi.offer(x);
19         while(!qt.isEmpty())
20         {
21             qi.offer(qt.poll());
22         }
23     }
24     
25     /** Removes the element on top of the stack and returns that element. */
26     public int pop() {
27         return qi.poll();
28     }
29     
30     /** Get the top element. */
31     public int top() {
32         return qi.peek();
33     }
34     
35     /** Returns whether the stack is empty. */
36     public boolean empty() {
37         return qi.isEmpty();
38     }
39 }
40 
41 /**
42  * Your MyStack object will be instantiated and called as such:
43  * MyStack obj = new MyStack();
44  * obj.push(x);
45  * int param_2 = obj.pop();
46  * int param_3 = obj.top();
47  * boolean param_4 = obj.empty();
48  */

 

 

  1.2 用栈实现队列

  

 

  •  问题分析

  使用两个栈来实现一个队列,即由先入后出改成先入先出,一个栈作为辅助栈,一个栈作为主栈   

 

  •  代码参考
 1 class Solution
 2 {
 3 public:
 4     void push(int node) {
 5         while(!stack2.empty())
 6         {
 7             stack1.push(stack2.top());
 8             stack2.pop();
 9         }
10         stack1.push(node);
11         while(!stack1.empty())
12         {
13             stack2.push(stack1.top());
14             stack1.pop();
15         }
16     }
17 
18     int pop() {
19         if(stack2.empty())
20             return 0;
21         int temp=stack2.top();
22         stack2.pop();
23         return temp;
24     }
25 
26 private:
27     stack<int> stack1;//主栈
28     stack<int> stack2;//辅助栈
29 };
  •  JAVA参考代码
 1 class MyQueue {
 2 
 3     /*
 4         栈是先进后出
 5         队列是先进先出
 6         要用栈实现队列,可以利用辅助栈,两个栈,一个主栈,一个辅助栈
 7         如果主栈有元素,插入元素时,先将主栈入辅助栈,将新的元素插入主栈后再将辅助栈的元素入主栈
 8     */
 9     //首先申请两个栈,一个主栈一个辅助栈
10     Stack<Integer> si;
11     Stack<Integer> st;
12     /** Initialize your data structure here. */
13     public MyQueue() {
14         si=new Stack<Integer>();
15         st=new Stack<Integer>();
16     }
17     
18     /** Push element x to the back of queue. */
19     public void push(int x) {
20         //如果主栈中有元素,则将主栈中所有元素入辅助栈
21         while(!si.isEmpty())
22         {
23             st.push(si.peek());
24             si.pop();
25         }
26         //然后将待插入元素入主栈
27         si.push(x);
28         //然后将辅助栈中元素入主栈
29         while(!st.isEmpty())
30         {
31             si.push(st.peek());
32             st.pop();
33         }
34     }
35     
36     /** Removes the element from in front of queue and returns that element. */
37     public int pop() {
38         int temp=si.peek();
39         si.pop();
40         return temp;
41     }
42     
43     /** Get the front element. */
44     public int peek() {
45         return si.peek();
46     }
47     
48     /** Returns whether the queue is empty. */
49     public boolean empty() {
50         return si.isEmpty();
51     }
52 }
53 
54 /**
55  * Your MyQueue object will be instantiated and called as such:
56  * MyQueue obj = new MyQueue();
57  * obj.push(x);
58  * int param_2 = obj.pop();
59  * int param_3 = obj.peek();
60  * boolean param_4 = obj.empty();
61  */

 

2. 借助辅助栈实现

2.1 包含min函数的栈

  

 

  •  问题分析

  要实现包含min函数的栈,即实现一个最小栈,最小栈的栈顶就是当前元素中的最小值

  使用两个栈来实现:数据栈和最小栈,数据栈每次正常压入数据,最小栈每次压入最小数据,设待插入元素为x,如最小栈为空或者x小于栈顶元素,则将x插入最小栈,否则将栈顶元素插入到最小栈中

 

  •  代码参考
 1 class MinStack {
 2 public:
 3     /** initialize your data structure here. */
 4     MinStack() {
 5 
 6     }
 7     stack<int> data;
 8     stack<int> mmin;
 9     
10     void push(int x) {
11         //数据栈正常压入数据
12         data.push(x);
13         if(mmin.empty()||x<mmin.top())
14         {
15             mmin.push(x);
16         }
17         else
18             mmin.push(mmin.top());
19     }
20     
21     void pop() {
22         if(!data.empty()&&!mmin.empty())
23         {
24             data.pop();
25             mmin.pop();
26         }
27     }
28     
29     int top() {
30         return data.top();
31     }
32     
33     int min() {
34         return mmin.top();
35     }
36 };
37 
38 /**
39  * Your MinStack object will be instantiated and called as such:
40  * MinStack* obj = new MinStack();
41  * obj->push(x);
42  * obj->pop();
43  * int param_3 = obj->top();
44  * int param_4 = obj->min();
45  */

 

  • JAVA代码参考
 1 class MinStack {
 2 
 3     //要实现包含Min函数的栈,需要用两个栈来实现,一个栈正常插入删除数据,一个栈存入当前最小元素
 4     /*
 5         主栈正常插入数据,对于最小栈
 6         当栈为空,则主栈和最小栈均插入待插入元素
 7         当栈非空且待插入元素小于栈顶元素,则主栈和最小栈均插入待插入元素
 8         当栈非空且待插入元素大于栈顶元素,则主栈正常插入元素,最小栈插入栈顶元素
 9     */
10     /** initialize your data structure here. */
11     Stack<Integer> mystack;
12     Stack<Integer> MinStack;
13     public MinStack() {
14         mystack=new Stack<Integer>();
15         MinStack=new Stack<Integer>();
16     }
17     
18     public void push(int x) {
19         //主栈正常插入数据
20         mystack.push(x);
21         //若当前栈不为空且待插入元素小于栈顶元素,则直接将栈顶元素插入
22         if(!MinStack.empty()&&x>MinStack.peek())
23             MinStack.push(MinStack.peek());
24         else
25             MinStack.push(x);
26     }
27     
28     public void pop() {
29         MinStack.pop();
30         mystack.pop();
31     }
32     
33     public int top() {
34         return mystack.peek();
35     }
36     
37     public int min() {
38         return MinStack.peek();
39     }
40 }
41 
42 /**
43  * Your MinStack object will be instantiated and called as such:
44  * MinStack obj = new MinStack();
45  * obj.push(x);
46  * obj.pop();
47  * int param_3 = obj.top();
48  * int param_4 = obj.min();
49  */

 

2.2 栈的最小值

  

 

  •  问题分析

  可以设置两个栈

  一个栈正常插入数据,一个栈插入最小元素

  入栈时:

    数据栈正常压入数据

    对于最小栈,若待插入元素小于最小栈栈顶元素,则将待插入元素入栈,否则将最小栈栈顶元素入栈

  出栈时:

    数据栈正常弹出数据

    最小栈正常弹出数据

  • C++代码参考
 1 class MinStack {
 2 public:
 3     /*
 4         可以设置两个栈
 5         一个数据栈正常插入数据dataStack
 6         一个最小栈插入最小值
 7         入栈时:
 8             数据栈正常插入数据
 9             最小栈插入最小值,若当前元素小于栈顶元素,则将当前元素入栈;若当前元素大于栈顶元素,则将栈顶元素入栈
10     */
11     stack<int> dataStack;
12     stack<int> minStack;
13     /** initialize your data structure here. */
14     MinStack() {
15 
16     }
17     
18     void push(int x) {
19         dataStack.push(x);
20         if(minStack.empty()||x<minStack.top())
21             minStack.push(x);
22         else
23             minStack.push(minStack.top());
24     }
25     
26     void pop() {
27         dataStack.pop();
28         minStack.pop();
29     }
30     
31     int top() {
32         return dataStack.top();
33     }
34     
35     int getMin() {
36         return minStack.top();
37     }
38 };
39 
40 /**
41  * Your MinStack object will be instantiated and called as such:
42  * MinStack* obj = new MinStack();
43  * obj->push(x);
44  * obj->pop();
45  * int param_3 = obj->top();
46  * int param_4 = obj->getMin();
47  */
  • JAVA代码参考

 

 1 class MinStack {
 2     /*
 3         可以使用两个栈来实现找到栈的最小值:一个常规数据栈datastack,一个最小栈minstack
 4         入栈时:
 5             数据栈正常压入数据
 6             对于最小栈,若待插入元素小于栈顶元素,则将待插入元素入最小栈,否则,将栈顶元素压入最小栈
 7         出栈时:
 8             数据栈弹出数据
 9             最小栈弹出数据
10     */
11     Stack<Integer> dataStack;
12     Stack<Integer> minStack;
13 
14     /** initialize your data structure here. */
15     public MinStack() {
16         dataStack=new Stack<Integer>();
17         minStack=new Stack<Integer>();
18     }
19     
20     public void push(int x) {
21         //数据栈正常入栈
22         //对于最小栈,若待插入元素小于栈顶元素,则待插入元素入最小栈,否则,将栈顶元素入最小栈
23         dataStack.push(x);
24         if(minStack.empty()||x<minStack.peek())
25             minStack.push(x);
26         else
27             minStack.push(minStack.peek());
28     }
29     
30     public void pop() {
31         //出栈时,数据栈和最小栈均弹出数据
32         dataStack.pop();
33         minStack.pop();
34     }
35     
36     public int top() {
37         return dataStack.peek();
38     }
39     
40     public int getMin() {
41         return minStack.peek();
42     }
43 }
44 
45 /**
46  * Your MinStack object will be instantiated and called as such:
47  * MinStack obj = new MinStack();
48  * obj.push(x);
49  * obj.pop();
50  * int param_3 = obj.top();
51  * int param_4 = obj.getMin();
52  */

 

 

 

3. 二叉树的几种遍历方式

  3.1 二叉树的前序遍历

  

 

  •  问题分析

  二叉树的前序遍历序列,即先访问根节点,再访问左子树节点,再访问右子树节点

  要实现二叉树的前序遍历序列,即借助辅助栈,从根节点开始,每次迭代弹出当前栈顶元素,将其孩子节点压入栈中,先压右子树节点,再压入左子树节点

  在二叉树的先序遍历中,每个节点会被访问两次,第一次是入栈,此时就输出,第二次是出栈

 

   

 

  •  代码参考
 1 /**
 2  * Definition for a binary tree node.
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<int> preorderTraversal(TreeNode* root) {
13         vector<int> B;
14         if(root==nullptr)
15             return B;
16         stack<TreeNode*> s;
17         s.push(root);
18         while(!s.empty())
19         {
20             TreeNode* t=s.top();
21             B.push_back(t->val);
22             s.pop();
23             if(t->right)
24                 s.push(t->right);
25             if(t->left)
26                 s.push(t->left);
27         }
28         return B;
29     }
30 };
  •  JAVA代码参考
 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode() {}
 8  *     TreeNode(int val) { this.val = val; }
 9  *     TreeNode(int val, TreeNode left, TreeNode right) {
10  *         this.val = val;
11  *         this.left = left;
12  *         this.right = right;
13  *     }
14  * }
15  */
16 class Solution {
17     public List<Integer> preorderTraversal(TreeNode root) {
18         /*
19             二叉树的前序遍历:先访问根节点,再访问左子树节点,再访问右子树节点
20             要实现二叉树的前序遍历,可以借助辅助栈,每次迭代弹出当前栈顶元素,然后将其孩子节点入栈,先压入右子树节点,再压入左子树节点
21         */
22         List<Integer> list=new ArrayList<Integer>();
23         if(root==null)
24             return list;
25         Stack<TreeNode> s=new Stack<TreeNode>();
26         //首先将根节点压入
27         s.push(root);
28         while(!s.empty())
29         {
30             //首先将栈顶元素压入
31             root=s.peek();
32             list.add(root.val);
33             //然后将栈顶元素弹出
34             s.pop();
35             //首先将右子树节点压入
36             if(root.right!=null)
37                 s.push(root.right);
38             //然后将左子树节点压入
39             if(root.left!=null)
40                 s.push(root.left);
41         }
42         return list;
43     }
44 }

 

  3.2 二叉树的中序遍历

  

 

  •  问题分析

  二叉树的中序遍历,即先访问左子树节点,再访问根节点,再访问右子树节点。因此要实现二叉树的中序遍历,可以借助辅助栈来实现

  在二叉树的中序遍历中,每个节点会被访问两次,第一次是入栈且不输出,第二次是出栈,输出

  

 

   

 

   

 

  •  代码参考
 1 /**
 2  * Definition for a binary tree node.
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<int> inorderTraversal(TreeNode* root) {
13         vector<int> B;
14         if(root==nullptr)
15             return B;
16         stack<TreeNode*> s;
17         //TreeNode* t=root;
18         while(!s.empty()||root!=nullptr)
19         {
20             while(root!=nullptr)
21             {
22                 s.push(root);
23                 root=root->left;
24             }
25             root=s.top();
26             B.push_back(root->val);
27             s.pop();
28             
29             root=root->right;
30         }
31         return B;
32     }
33 };
  •  JAVA代码参考
 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode() {}
 8  *     TreeNode(int val) { this.val = val; }
 9  *     TreeNode(int val, TreeNode left, TreeNode right) {
10  *         this.val = val;
11  *         this.left = left;
12  *         this.right = right;
13  *     }
14  * }
15  */
16 class Solution {
17     public List<Integer> inorderTraversal(TreeNode root) {
18         /*
19             二叉树的中序遍历:首先访问左子树节点,再访问根节点,最后访问右子树节点
20             可以申请一个辅助栈。当节点的左子树不为空时,一直入栈
21             当节点的右子树不为空时,将右子树压入
22         */
23         List<Integer> list=new ArrayList<Integer>();
24         if(root==null)
25             return list;
26         Stack<TreeNode> s=new Stack<TreeNode>();
27         while(!s.empty()||root!=null)
28         {
29             //如果根节点的左子树节点不为空,则一直将根节点的左子树节点压入
30             while(root!=null)
31             {
32                 s.push(root);
33                 root=root.left;
34             }
35             root=s.peek();
36             list.add(root.val);
37             s.pop();
38 
39             root=root.right;
40         }
41         return list;
42     }
43 }

 

  3.3 二叉树的后序遍历

  

 

  •  问题分析

  类似中序遍历,首先一直访问右子树节点,访问一个输出一个,然后弹出,若该结点存在左子树节点,则直接入栈,全部访问后将数组逆置

 

 

  • 代码参考
 1 /**
 2  * Definition for a binary tree node.
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<int> postorderTraversal(TreeNode* root) {
13         //后序遍历顺序中每个节点被访问三次
14         //第一次:
15         vector<int> B;
16         if(root==nullptr)
17             return B;
18         stack<TreeNode*> s;
19         while(!s.empty()||root)
20         {
21             while(root!=nullptr)
22             {
23                 B.push_back(root->val);
24                 s.push(root);
25                 root=root->right;
26             }
27             root=s.top();
28             s.pop();
29             root=root->left;
30         }
31         reverse(B.begin(),B.end());
32         return B;
33     }
34 };
  •  JAVA代码参考
 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode() {}
 8  *     TreeNode(int val) { this.val = val; }
 9  *     TreeNode(int val, TreeNode left, TreeNode right) {
10  *         this.val = val;
11  *         this.left = left;
12  *         this.right = right;
13  *     }
14  * }
15  */
16 class Solution {
17     public List<Integer> postorderTraversal(TreeNode root) {
18         ArrayList<Integer> list=new ArrayList<Integer>();
19         if(root==null)
20             return list;
21         Stack<TreeNode> s=new Stack<TreeNode>();
22         while(!s.empty()||root!=null)
23         {
24             //只要其存在右子树节点,则直接将右子树节点入栈
25             while(root!=null)
26             {
27                 s.push(root);
28                 list.add(root.val);
29                 root=root.right;
30             }
31             root=s.peek();
32             s.pop();
33             
34 
35             root=root.left;
36         }
37         Collections.reverse(list);
38         return list;
39     }
40 }

 

 

  3.4 二叉树的层序遍历

  3.5 二叉树的之字形遍历

  

 

 

  • 问题分析

  要实现二叉树的之字形打印,可以用两个辅助栈实现,当前排数为奇数时,从右向左压栈,否则,从左到右压栈

 

  •  题目描述

  

 1 /**
 2  * Definition for a binary tree node.
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
13         vector<vector<int>> B;
14         if(root==nullptr)
15             return B;
16         vector<int> line;
17         int cur=0;
18         int next=1;
19         stack<TreeNode*> events[2];
20         events[cur].push(root);
21         while(!events[0].empty()||!events[1].empty())
22         {
23             TreeNode* t=events[cur].top();
24             line.push_back(t->val);
25             events[cur].pop();
26             if(cur==0)
27             {
28                 if(t->left)
29                     events[next].push(t->left);
30                 if(t->right)
31                     events[next].push(t->right);
32             }
33             else
34             {
35                 if(t->right)
36                     events[next].push(t->right);
37                 if(t->left)
38                     events[next].push(t->left);
39             }
40             if(events[cur].empty())
41             {
42                 B.push_back(line);
43                 line.clear();
44                 cur=1-cur;
45                 next=1-next;
46             }
47         }
48         return B;
49     }
50 };
  •  JAVA代码参考

  思路分析:使用广度优先搜索,对树进行逐层遍历,用队列维护当前的所有元素,当队列不为空时,求得当前队列的长度size,每次从队列中取出size个元素进行扩展,然后进行下一次迭代

  可以利用双端队列的数据结构来维护当前层节点输出的顺序

  双端队列是一个可以在任意一段插入元素的队列,广度优先搜索遍历当前层节点扩展下一层节点时我们仍从左往右按顺序扩展,但是对当前层节点的存储我们维护一个变量isLeftOrder记录是从左至右还是从右至左

  • 如果从左至右,则每次将遍历到的元素插入到双端队列的末尾
  • 如果从右至左,则每次将遍历到的元素插入至双端队列的头部
 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode(int x) { val = x; }
 8  * }
 9  */
10 class Solution {
11     public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
12         List<List<Integer>> list=new LinkedList<List<Integer>>();
13         if(root==null)
14             return list;
15         Queue<TreeNode> nodeQueue=new LinkedList<TreeNode>();
16         nodeQueue.offer(root);
17         boolean isLeftOrder=true;
18         while(!nodeQueue.isEmpty())
19         {
20             //记录每一层节点的值
21             Deque<Integer> level=new LinkedList<Integer>();
22             int count=nodeQueue.size();
23             for(int i=0;i<count;++i)
24             {
25                 TreeNode curNode=nodeQueue.poll();
26                 //先访问左子树节点,再访问右子树节点
27                 if(isLeftOrder)
28                 {
29                     level.offerLast(curNode.val);
30                 }
31                 //否则,先访问右子树节点,再访问左子树节点
32                 else
33                     level.offerFirst(curNode.val);
34                 
35                 if(curNode.left!=null)
36                     nodeQueue.offer(curNode.left);
37                 if(curNode.right!=null)
38                     nodeQueue.offer(curNode.right);
39             }
40             list.add(new LinkedList<Integer>(level));
41             isLeftOrder=!isLeftOrder;
42         }
43         return list;
44     }
45 }

 

4. 括号的匹配

  4.1. 有效的括号

  

 

 

  •  问题分析

  要判断有效的字符串,可以借助辅助栈来进行实现,从头到尾扫描字符串

    • 如果遇到左边字符串"{ [ (",则直接将左边字符串入栈
    • 如果遇到右边字符串") ] }"
      • 如果栈为空,则字符串无效
      • 如果栈不为空,则将栈顶元素出栈,与右字符串进行匹配
        • 如果右字符串不匹配,则说明括号无效
        • 如果右字符串匹配("{"和"}","["和"]","("和")"),则继续匹配下一个字符  
    • 扫描完整个字符串后
      • 如果栈为空,则括号匹配
      • 如果栈不为空,则括号不匹配
  • 代码参考
 1 class Solution {
 2 public:
 3     bool isValid(string s) {
 4         /*
 5         利用栈来实现
 6         如果遇到左边括号{[(,则将左边字符入栈
 7         如果遇到右边括号)]}
 8             如果栈为空,则为无效字符
 9             如果栈不为空,将栈顶字符弹出,与右边字符进行匹配
10                 如果右边字符不匹配,则为无效括号
11                 如果右边字符匹配,则为有效括号
12         扫描完整个字符串后
13             如果栈为空,则为有效括号
14             如果栈不为空,则为无效括号
15         */
16         stack<char> si;
17         int len=s.length();
18         for(int i=0;i<len;++i)
19         {
20             if(s[i]=='{'||s[i]=='['||s[i]=='(')
21                 si.push(s[i]);
22             else
23             {
24                 if(si.empty())
25                     return false;
26                 char left=si.top();
27                 si.pop();
28                 if(left=='{'&&s[i]!='}')
29                     return false;
30                 if(left=='['&&s[i]!=']')
31                     return false;
32                 if(left=='('&&s[i]!=')')
33                     return false;
34             }
35         }
36         return si.empty();
37     }
38 };
  •  JAVA代码参考
 1 class Solution {
 2     public boolean isValid(String s) {
 3         /*
 4             将字符串中的字符入栈
 5             若其为左边括号(,[,{,则直接将左边括号入栈
 6             若其为右边括号),],}
 7                 若当前栈为空,则不匹配,返回fasle
 8                 若当前栈非空且栈顶元素与待插入字符是一对,则直接将待插入元素弹出,且指向字符串中下一个字符
 9                 若当前栈非空且栈顶元素与待插入字符不配套,则返回false
10         */
11         if(s.length()==0)
12             return true;
13         int size=s.length();
14         Stack<Character> si=new Stack<Character>();
15         for(int i=0;i<size;++i)
16         {
17             //如果其为左边括号,则直接将左边括号入栈
18             if(s.charAt(i)=='('||s.charAt(i)=='['||s.charAt(i)=='{')
19                 si.push(s.charAt(i));
20             else
21             {
22                 //如果当前栈为空,则直接返回false
23                 if(si.empty())
24                     return false;
25                 char left=si.peek();
26                 si.pop();
27                 if(left=='('&&s.charAt(i)!=')')
28                     return false;
29                 if(left=='['&&s.charAt(i)!=']')
30                     return false;
31                 if(left=='{'&&s.charAt(i)!='}')    
32                     return false;
33             }
34         }
35         return si.empty();
36     }
37 
38 }

 

  4.2. 字符串解码

  

 

  • 问题分析

  要实现字符串解码,我们可以用两个辅助栈来实现,一个辅助栈存储数字,一个辅助栈存储字符

  此外,利用两个辅助变量,num保存数字,res保存字符串

  算法如下

    从头到尾遍历整个数组

      若当前字符c为数字,则将数字字符转换为数字

      若当前字符c为字母,则将字母字符添加到res后

      若当前字符c为左括号[,将res和num分别入栈,并分别置空置零

        记录res的临时结果res至栈,用于发现对应]后,获取timesx[....]字符

      若当前字符c为有括号],将两个栈顶元素出栈,并将res中元素循环相应的nums.top()次

  

  • 代码参考
 1 class Solution {
 2 public:
 3     string decodeString(string s) {
 4         /*
 5         利用两个辅助栈来实现,一个栈存储数字,一个栈存储字符串
 6         借助两个辅助变量,num来保存数字,res保存字符串
 7         从头到尾遍历整个数组
 8         若当前字符c为数字,则将数字字符转换为数字
 9         若当前字符c为字母,则将字符c添加到res后面
10         若当前字符c为[,则将目前有的nums和res分别入栈
11         若当前字符c为],则将栈顶元素出栈,并且res中的元素循环相应的num次
12         */
13         stack<int> nums;
14         stack<string> strs;
15         int num=0;
16         string res="";
17         int len=s.size();
18         for(int i=0;i<len;++i)
19         {
20             //如果为数字,则将数字字符转换为数字
21             if(s[i]>='0'&&s[i]<='9')
22             {
23                 num=num*10+s[i]-'0';
24             }
25             //如果为字符,则直接在res后面添加
26             else if((s[i]>='a'&&s[i]<='z')||s[i]>='A'&&s[i]<='Z')
27             {
28                 res=res+s[i];
29             }
30             //如果为字符[,则分别将num和res入栈,并且将num和res置零
31             else if(s[i]=='[')
32             {
33                 nums.push(num);
34                 num=0;
35                 strs.push(res);
36                 res="";
37             }
38             //如果为字符']'
39             else
40             {
41                 int times=nums.top();
42                 nums.pop();
43                 for(int j=0;j<times;++j)
44                 {
45                     strs.top()+=res;
46                 }
47                 res=strs.top();
48                 strs.pop();
49             }
50         }
51         return res;
52     }
53 };
  •  JAVA代码参考
 1 class Solution {
 2     public String decodeString(String s) {
 3         /*
 4             要实现字符串的解码,我们需要从头到尾扫描整个字符串,整个过程当前字符可能出现的情况是:当前字符为数字,当前字符为字符,当前字符为[,当前字符为],因此需要分为以下几种情况考虑
 5             借助两个辅助栈,一个存储数字,一个存储数组,由于数字可能不止是个位的,字符也可能不止一个字符,因此需要借助两个辅助变量:num保存数字,res保存变量
 6             若当前字符c是数字,则利用num进行存储,假设字符串中是321[a],则数字num应该是321,即num=num*10+c-'0';
 7             若当前字符c是字母,则将c添加到res后面
 8             若当前字符c是[,则直接将num和res入栈
 9             若当前字符c是],则将数组栈和字符栈元素出栈,并且将字符栈栈顶元素重复数字栈的栈顶元素次
10         */
11         //首先申请两个辅助栈,一个存储数字,一个存储字符串
12         Stack<Integer> nums=new Stack<Integer>();
13         Stack<String> strs=new Stack<String>();
14         //申请两个辅助变量:num用于存储数字,res用于存储字符串
15         int num=0;
16         String res="";
17         //从头到尾遍历整个字符串
18         int len=s.length();
19         for(int i=0;i<len;++i)
20         {
21             char c=s.charAt(i);
22             //如果当前字符c是数字,则直接将其存储在num上
23             if(c>='0'&&c<='9')
24             {
25                 num=num*10+c-'0';
26             }
27             //如果当前字符c是字母,则将其拼接在res的后面
28             else if((c>='a'&&c<='z')||(c>='A'&&c<='Z'))
29             {
30                 res=res+c;
31             }
32             //如果当前字符c是'[',则直接将num和str分别入栈,并且将num置零,res置为空字符串
33             else if(c=='[')
34             {
35                 nums.push(num);
36                 num=0;
37                 strs.push(res);
38                 res="";
39             }
40             //如果当前字符c是']',则分别将数字栈和字符串栈的栈顶元素出栈,并且将字符串栈的栈顶元素重复数字栈的栈顶元素这么多次
41             else
42             {
43                 int times=nums.peek();
44                 nums.pop();
45                 //将字符串栈的栈顶元素重复数字栈的栈顶元素这么多次
46                 String temp="";
47                 for(int j=0;j<times;++j)
48                 {
49                     temp+=res;
50                 }
51                 res=new String(strs.peek()+temp);
52                 strs.pop();
53             }
54             
55         }
56         return res;
57     }
58 }

 

 4.3 逆波兰表达式求值

  

  • 问题分析

  对于此类有运算符号或者括号的情况,一般是分情况进行处理

 

 

   使用栈来存放每个数字,每当遍历到运算符时,弹出栈顶的两个元素,用第二个栈顶元素操作第一个

  加法和乘法没有顺序,减法和触发注意先后问题即可。

  • JAVA代码参考
 1 class Solution {
 2     public int evalRPN(String[] tokens) {
 3         /*
 4             对于此种符号类型的表达式,我们一般使用栈来操作
 5             若当前字符为数字时,则直接将数字入栈
 6             若当前字符为+,-,*,/时,则直接出栈两个进行运算,并将运算结果再次入栈
 7         */
 8         int res=0;
 9         Stack<Integer> s=new Stack<Integer>();
10         for(int i=0;i<tokens.length;++i)
11         {
12             //若当前字符串是+,-,*,/,则需要将栈中数据弹出,且需要注意顺序
13             //若当前字符串是"+",则不需要注意顺序
14             //判断字符串是否相等记得用.equals()
15             if(tokens[i].equals("+"))
16             {
17                 res=s.pop()+s.pop();
18                 s.push(res);
19             }
20             //若当前字符串是"-",则需要用先入栈的减去后入栈的
21             else if(tokens[i].equals("-"))
22             {
23                 int substraction=s.pop();
24                 int minuend=s.pop();
25                 res=minuend-substraction;
26                 s.push(res);
27             }
28             //若当前字符串是"*",则不需要注意顺序
29             else if(tokens[i].equals("*"))
30             {
31                 res=s.pop()*s.pop();
32                 s.push(res);
33             }
34             else if(tokens[i].equals("/"))
35             {
36                 int divisor=s.pop();
37                 int dividend=s.pop();
38                 res=dividend/divisor;
39                 s.push(res);
40             }
41             else
42                 s.push(Integer.valueOf(tokens[i]));
43         }
44         return s.pop();
45     }
46 }

 

4.4 棒球比赛

 

  • 问题分析

  可以采用栈来实现,维护一个得分的栈

  由于涉及到加,倍数或者无效的操作,使用栈来实现

  从头到尾遍历整个字符串数组

  若当前字符串是"+",则将前两次得到的数据相加后入栈即可

  若当前字符串是"D",则将前一次得到的数据乘以2后入栈

  若当前字符串是"C",则将栈顶元素移除即可

  • 代码参考
 1 class Solution {
 2     public int calPoints(String[] ops) {
 3         /*
 4             目前比较重要的是维护这个得分的栈
 5             由于涉及到加,倍数或者无效的操作,因此我们可以使用栈来实现
 6             遍历整个字符串数组
 7             若当前字符串为"+",则将前两次得到的数据相加后入栈即可
 8             若当前字符串是"D",则将前一次得到的数据乘以2后入栈即可
 9             若当前字符串是"C",则将栈顶元素移除即可
10         */
11         Stack<Integer> s=new Stack<Integer>();
12         int res=0;
13         //首先遍历整个字符串数组
14         for(int i=0;i<ops.length;++i)
15         {
16             //若当前字符串是"+",则将前两次得到的数据相加后入栈即可
17             if(ops[i].equals("+"))
18             {
19                 int firstScore=s.pop();
20                 int secondScore=s.pop();
21                 res=firstScore+secondScore;
22                 s.push(secondScore);
23                 s.push(firstScore);
24                 s.push(res);
25             }
26             //若当前字符串是"D",则将栈顶元素乘以2后入栈
27             else if(ops[i].equals("D"))
28             {
29                 res=s.peek()*2;
30                 s.push(res);
31             }
32             //若当前字符串是"C",则将栈顶元素出栈
33             else if(ops[i].equals("C"))
34             {
35                 s.pop();
36             }
37             else
38             {
39                 s.push(Integer.valueOf(ops[i]));
40             }
41         }
42         int sum=0;
43         while(!s.empty())
44         {
45             sum+=s.pop();
46         }
47         return sum;
48     }
49 }

 

 4.5 基本计算器2

  

 

 

  •  问题分析

  有了加减乘除之后,就有了优先级

  解决方式是将乘除运算转换为加减运算

  先把乘除的值计算出来,最终将所有的运算转换为加法

  首先将字符串收尾的空格删除

  若字符串中间有空格,遇到空格则跳过空格

  若出现了数字则记录整个数字是多少,然后根据之前的运算符决定下一步

  如果出现加号'+',说明前面的运算独立于之后的运算,可以将结果暂时放入栈

  如果出现减号'-',可以看成-1*num,然后将-num入栈

  如果出现乘号'*'或者除号'/',由于前面的运算独立于此,可以先计算目前元素和栈顶元素的积或者除法,然后结果入栈

  最后将栈中所有元素相加就是答案

  • JAVA代码参考
 1 class Solution {
 2     public int calculate(String s) {
 3         //首先将字符串前后的空格去掉
 4         s=s.trim();
 5         return core(s.toCharArray());
 6     }
 7     private int i=0;
 8     private int core(char[] str) {
 9         /*
10             有了加减乘数之后,就有了优先级
11             解决方法是将其最终转换为加减运算
12             先把乘除的值计算出来,最终将所有的运算简化成只有加法
13                 首先遇到空格则跳过空格
14                 若出现了数字则记录整个数字是多少,然后根据之前的运算符决定下一步
15                 如果是加号'+',说明前面的运算独立于以后的运算,可以将结果暂时放入栈
16                 如果是减号'-',可以看成-1*num,然后将-num入栈
17                 如果是乘号'*'或者除号'/',由于前面的运算独立于此,可以先计算目前元素和栈顶元素的积或者除法,然后结果入栈
18                 最后将栈中所有元素相加就是答案。
19         */
20         //申请一个字符栈
21         Stack<Integer> st=new Stack<>();
22         char operater='+';
23         int num=0;
24         //首先遍历整个字符数组
25         for(;i<str.length;++i)
26         {
27             //如果遇到空格,则直接跳过
28             if(str[i]==' ')
29                 continue;
30             //由于是字符串,若为几位数的数字则需要直接将其转换为一个数字
31             if(Character.isDigit(str[i]))
32             {
33                 num=num*10+(str[i]-'0');
34             }
35             else if(str[i]=='(')
36             {
37                 ++i;
38                 num=core(str);
39                 ++i;
40             }
41                 
42             else if(str[i]==')')
43                 break;
44             //若其不是数字,则其就为几种运算符+,-,*,/
45             //i=str.length-1是为了处理最后一个数字
46             if(!Character.isDigit(str[i])||i>=str.length-1)
47             {
48                 //如果当前操作数为加,则将num入栈
49                 if(operater=='+')
50                     st.push(num);
51                 //若当前操作数为减,则将-num入栈
52                 else if(operater=='-')
53                     st.push(-num);
54                 //若当前操作数为乘,则将当前元素和栈顶元素相乘,将相乘后的结果入栈
55                 else if(operater=='*')
56                 {
57                     int a=st.pop();
58                     st.push(a*num);
59                 }
60                 //若当前操作数为/,则将栈顶元素除以当前元素,并将最后结果入栈
61                 else if(operater=='/')
62                 {
63                     int a=st.pop();
64                     st.push(a/num);
65                 }
66                 //若出现越界,则直接停止
67                 if(i>str.length-1)
68                     break;
69                 //更新操作数
70                 operater=str[i];
71                 num=0;
72             }         
73         }
74         //最终栈中的元素全部应该是相加操作
75         int sum=0;
76         while(!st.empty())
77         {
78             sum+=st.pop();
79         }
80         return sum;
81     }
82 }

 

 4.6 移除无效的括号

  

 

  •  问题分析

  首先遍历整个字符串,使用栈来存储左边括号的索引,使用数组removeArr保存需要被删除的括号的下标

    若当前字符为左边括号,则将当前字符的索引入栈

    若当前字符为右边括号

      若当前栈为空,则证明没有与其匹配的左边括号,则此右边括号是需要被删除的,将右边括号的索引使用removeArr保存

      若当前栈非空,则证明有与其匹配的左边括号,此时将栈顶元素出栈

  遍历字符串,当下标不再需要删除的数组中的字符是我们需要的

  • JAVA代码参考

  

 1 class Solution {
 2     public String minRemoveToMakeValid(String s) {
 3         /*
 4             开始一遍遍历字符串,用栈保存左括号的下标
 5                 当遇到右括号且栈非空时,栈顶元素出栈
 6                 当遇到有括号且栈为空时,将右括号的下标保存到需要删除的数组中
 7             最后遍历字符串,当下标不再需要删除的数组中时,字符串是我们需要的
 8         */
 9         if(s.length()==0||s==null)
10             return s;
11         Stack<Integer> st=new Stack<Integer>();
12         int n=s.length();
13         int[] removeArr=new int[n];
14         //一次遍历字符串
15         for(int i=0;i<n;++i)
16         {
17             //如果遇到左边括号(,则将右边括号的下标入栈
18             char cur=s.charAt(i);
19             if(cur=='(')
20                 st.push(i);
21             //如果遇到右边括号
22             else if(cur==')')
23             {
24                 //如果此时栈为空,则证明没有与右边括号匹配的左边括号,此右边括号应被删除
25                 if(st.empty())
26                 {
27                     removeArr[i]=1;
28                 }
29                 else
30                     st.pop();
31             }
32         }
33         //经过上边步骤,应该被删除的右边括号的索引已经确定
34         //下面需要确定应该被删除的左边括号的索引
35         while(!st.empty())
36         {
37             removeArr[st.pop()]=1;
38         }
39         //此时,在removeArr中,需要被删除的标记为1,应该保留的为0,直接使用StringBuilder重构字符串即可
40         StringBuilder sb=new StringBuilder();
41         for(int i=0;i<n;++i)
42         {
43             if(removeArr[i]==0)
44                 sb.append(s.charAt(i));
45         }
46         return sb.toString();
47     }
48 }

 

4.7 括号的分数

  

 

  • 问题分析

  遍历字符串S中的每个字符

    • 若当前字符为左边括号(,则直接入栈
    • 若当前字符为右边括号),
      • 若前一个字符为(,则得分加2的n-1次方(n为栈的深度),然后弹出一个字符
      • 若前一个字符为),则直接弹出一个字符
  • 代码参考

  

 1 class Solution {
 2     public int scoreOfParentheses(String S) {
 3         if(S.length()==0)
 4             return 0;
 5         Stack<Character> st=new Stack<Character>();
 6         int score=0;
 7         for(int i=0;i<S.length();++i)
 8         {
 9             //如果当前字符为左边括号(,则直接将当前字符入栈
10             char temp=S.charAt(i);
11             if(temp=='(')
12             {
13                 st.push(temp);
14             }
15             //如果当前字符为右边括号),则看其上一个字符是否是左边括号,若上一个字符是左边括号,则加2的n-1次方栈的深度
16             else
17             {
18                 if(S.charAt(i-1)=='(')
19                 {
20                     score+=Math.pow(2,st.size()-1);
21                     st.pop();
22                 }
23                 else
24                     st.pop();
25             }
26         }
27         return score;
28     }
29 }

 

      

 

 

5. 单调栈

  所谓单调栈就是自栈顶到栈底元素单调递增/递减的栈

  单调栈中存放的数据是有序的,因此单调栈也分为单调递增栈和单调递减栈

    • 单调递增栈:栈中数据出栈顺序为单调递增序列,或者说:单调递增栈从栈底到栈顶递增
      • 栈为空,入栈
      • 栈非空且待插入元素大于栈顶元素,入栈
      • 栈非空且待插入元素小于栈顶元素,则出栈,直到待插入元素大于栈顶元素

    单调递增栈伪代码:

for(遍历整个数组)
{
        while(栈非空&&待插入元素<栈顶元素)
        {
                栈顶元素出栈;
          更新结果; } 待插入元素入栈; }
返回结果;

 

    • 单调递减栈:栈中数据出栈顺序为单调递减序列,或者说:单调递减栈从栈底到栈顶递减
      • 栈为空,则入栈
      • 栈非空且待插入元素小于栈顶元素,则入栈
      • 栈非空且待插入元素大于栈顶元素,则出栈,直到待插入元素小于栈顶元素

    单调递减栈伪代码

stack<int> st;
for(遍历这个数组)
{
        while(非栈空&&栈顶元素>当前待插入元素)
        {
                栈顶元素出栈;
                更新结果;
        }
         当前元素入栈;
}       
返回结果;         

 

单调栈主要回答这样的几种问题:

  比当前元素更大的下一个元素

  比当前元素更大的前一个元素

  比当前元素更小的下一个元素

  比当前元素更小的前一个元素

5.1. 每日温度

  

 

  •  问题分析

  输出是至少需要等待的天数比当天气温更高,因此我们可以借助栈来实现,由于需要看天数的间隔,我们将气温的下标入栈而不是直接将气温入栈。

  三种情况:

  1. 如果当前栈为空,则直接将气温下标入栈

  2. 如果当前栈非空并且待插入的元素小于栈顶元素,则直接将待插入的元素的下标入栈

  3. 如果当前栈非空并且待插入的元素大于栈顶元素,则将栈顶元素出栈,一直到待插入的元素小于栈顶元素

 

  •  代码参考
 1 class Solution {
 2 public:
 3     vector<int> dailyTemperatures(vector<int>& T) {
 4         /*
 5         如果栈为空,则入栈
 6         如果栈不为空,且待插入的元素小于栈顶元素,则直接入栈
 7         如果栈不为空且待插入的元素大于栈顶元素,则一直出栈,直到待插入的元素小于栈顶元素,再出栈
 8         */
 9         int len=T.size();
10         vector<int> B(len,0);
11         if(T.empty())
12             return B;
13         stack<int> index;
14         for(int i=0;i<len;++i)
15         {
16             //如果当前栈不为空,且待插入的元素大于栈顶元素,则一直出栈
17             while(!index.empty()&&T[i]>T[index.top()])
18             {
19                 int t=index.top();
20                 index.pop();
21                 B[t]=i-t;
22             }
23             index.push(i);
24         }
25         return B;
26     }
27 };
  •  JAVA代码参考
 1 class Solution {
 2     public int[] dailyTemperatures(int[] T) {
 3         /*
 4             要实现找到需要观测到更高气温,需要等待的天数,可以利用单调递减栈来实现
 5             由于输出的是观测到更高气温等待的天数,即数组中元素索引的间隔,因此我们将元素索引入栈
 6             从头到尾遍历整个数组
 7             如果当前栈为空,则直接将待插入元素入栈
 8             如果当前栈不为空且待插入元素小于栈顶元素,则直接将待插入元素入栈
 9             如果当前栈不为空且待插入元素大于栈顶元素,则将栈顶元素出栈,且输出数组中栈顶元素所对应的索引处元素值为待插入元素和栈顶元素的差值
10         */
11         int len=T.length;
12         int[] days=new int[len];
13         //申请一个栈
14         Stack<Integer> s=new Stack<Integer>();
15         //首先从头遍历整个数组
16         for(int i=0;i<len;++i)
17         {
18             //如果当前栈不为空且待插入元素大于栈顶元素,则将栈顶元素出栈,且输出数组中栈顶元素所对应所引处的值为待插入元素和栈顶元素的差值
19             while(!s.empty()&&T[i]>T[s.peek()])
20             {
21                 days[s.peek()]=i-s.peek();
22                 s.pop();              
23             }
24             //否则将元素的索引值入栈
25             s.push(i);
26         }
27         return days;
28     }
29 }

 

5.2 下一个更大元素1

  

 

  •  问题分析

  这道题我们可以利用单调栈的方法进行解决。由于nums1是nums2的子数组,因此我们先将nums2中的每个数组,求出其下一个更大的元素,并将nums2中的每个元素利用哈希表,建立nums2中的元素与其下一个更大元素之间的映射关系。再遍历nums1数组,直接找出答案。

    当栈为空时,直接将nums2[i]入栈

    当栈不为空时且nums2[i]<栈顶元素时,直接将nums2[i]入栈

    当栈不为空且nums2[i]>栈顶元素时,将栈顶元素出栈,并建立栈顶元素和nums[i]之间对应关系的哈希表,知道nums2[i]<栈顶元素

  然后遍历nums1数组,直接找出答案。

 

  •  代码参考
 1 class Solution {
 2 public:
 3     vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
 4         //出栈的时候更新哈希表
 5         vector<int> B;
 6         unordered_map<int,int> mymap;
 7         stack<int> s; 
 8         int len=nums2.size();
 9         for(int i=0;i<len;++i)
10         {
11             while(!s.empty()&&nums2[i]>s.top())
12             {
13                 mymap[s.top()]=nums2[i];
14                 s.pop();
15             }
16             s.push(nums2[i]);
17         }
18         while(!s.empty())
19         {
20             mymap[s.top()]=-1;
21             s.pop();
22         }
23         for(int i=0;i<nums1.size();++i)
24         {
25             B.push_back(mymap[nums1[i]]);
26         }
27         return B;
28     }
29 };
  •  JAVA代码参考
 1 class Solution {
 2     public int[] nextGreaterElement(int[] nums1, int[] nums2) {
 3         /*
 4             由于要找到nums1中的每个元素在nums2中的下一个比其更大的值,由于nums1是nums2的子数组,因此首先我们可以求出nums2数组中每个元素的下一个更大元素的数组,并利用哈希表建立nums2数组中每个元素与其下一个更大元素数组的映射关系,再遍历nums1,直接找出答案
 5         */
 6         //首先找出nums2数组中每个元素的下一个更大元素的数组,并利用哈希表建立nums2数组中每个元素与其下一个更大元素数组的映射关系
 7         int len=nums2.length;
 8         Stack<Integer> s=new Stack<Integer>();
 9         //申请一个哈希表建立nums2数组中每个元素与其下一个数组的映射关系
10         HashMap<Integer,Integer> map=new HashMap<>();
11         /*
12             遍历nums2数组
13             如果当前栈为空,则直接入栈
14             如果当前栈不为空且待插入元素小于栈顶元素,则直接将待插入元素插入
15             如果当前栈不为空且待插入元素大于栈顶元素,则栈顶元素与待插入元素之间的映射关系,即,栈顶元素的下一个更大元素的就是待插入的元素
16         */
17         for(int i=0;i<len;++i)
18         {
19             while(!s.empty()&&nums2[i]>nums2[s.peek()])
20             {
21                 map.put(nums2[s.peek()],nums2[i]);
22                 s.pop();
23             }
24             s.push(i);
25         }
26         //当栈不为空时,栈中剩余元素都没有下一个更大元素,直接置为-1即可
27         while(!s.empty())
28         {
29             map.put(nums2[s.peek()],-1);
30             s.pop();
31         }
32         //最后,遍历nums1数组既可以找到nums1中每个元素在nums2中的下一个比其更大的值
33         int lenofnum1=nums1.length;
34         int[] nextgreaterelementofNums1=new int[lenofnum1];
35         for(int i=0;i<lenofnum1;++i)
36         {
37             //使用HashMap中自带的get函数,可以实现HashMap中键值为key的值
38             nextgreaterelementofNums1[i]=map.get(nums1[i]);
39         }
40         return nextgreaterelementofNums1;
41     }
42 }

 

 

  5.3 下一个更大元素2

  

  •  问题分析

  我们可以采用单调栈来解决这个问题。

  两次扫描解决循环问题

  第一次扫描

    如果栈为空,则当前元素nums[i]直接入栈

    如果栈不为空且当前元素nums[i]<栈顶元素,则直接入栈

    如果栈顶元素不为空且当前元素nums[i]>栈顶元素,则将栈顶元素出栈,且B[s.top()]=nums[i],直到当前元素nums[i]<栈顶元素,再将nums[i]入栈

  第二次扫描是为了解决stack中剩下的,看是否能在前面找到更大的元素

    如果栈顶元素不为空且当前元素nums[i]>栈顶元素,则将栈顶元素出栈,且B[s.top()]=nums[i]

    否则直接扫描下一个元素

 

  •  代码参考
 1 class Solution {
 2 public:
 3     vector<int> nextGreaterElements(vector<int>& nums) {
 4         int len=nums.size();
 5         vector<int> B(len,-1);
 6         if(nums.empty())
 7             return B;
 8         stack<int> s;
 9         //两次扫描
10         for(int i=0;i<len;++i)
11         {
12             while(!s.empty()&&nums[i]>nums[s.top()])
13             {
14                 B[s.top()]=nums[i];
15                 s.pop();
16             }
17             s.push(i);
18         }
19         //再扫描一次,处理stack中剩下的,看能否在前面找到更大的元素
20         for(int i=0;i<len;++i)
21         {
22             while(!s.empty()&&nums[i]>nums[s.top()])
23             {
24                 B[s.top()]=nums[i];
25                 s.pop();
26             }
27         }
28         return B;
29     }
30 };
  •  JAVA参考代码
 1 class Solution {
 2     public int[] nextGreaterElements(int[] nums) {
 3         /*
 4             可以使用两次扫描来找到循环数组中下一个元素更大的元素
 5             每次扫描使用相同的策略
 6             如果当前栈为空,则直接插入
 7             如果当前栈非空且待插入元素小于栈顶元素,则直接将元素插入
 8             如果当前栈非空且待插入元素大于栈顶元素,则将栈顶元素弹出,且栈顶元素所在索引处的值为待插入元素
 9             将索引入栈
10         */
11         int len=nums.length;
12         int[] nextGreaterElementsofNums=new int[len];
13         for(int i=0;i<len;++i)
14             nextGreaterElementsofNums[i]=-1;
15         Stack<Integer> s=new Stack<Integer>();
16         //第一次扫描
17         for(int i=0;i<len;++i)
18         {
19             while(!s.empty()&&nums[i]>nums[s.peek()])
20             {
21                 nextGreaterElementsofNums[s.peek()]=nums[i];
22                 s.pop();
23             }
24             s.push(i);
25         }
26         //第二次扫描
27         for(int i=0;i<len;++i)
28         {
29             while(!s.empty()&&nums[i]>nums[s.peek()])
30             {
31                 nextGreaterElementsofNums[s.peek()]=nums[i];
32                 s.pop();
33             }
34             s.push(i);
35         }
36         return nextGreaterElementsofNums;
37     }
38 }

 

  5.4 最短无连续子数组

  

 

  •  问题分析

  采用单调栈来解决这个问题

  分为两个步骤:

    首先正向遍历,采用单调递增栈(从栈底到栈顶递增),找出自始至终没有出栈的最小索引l,单调递增栈特性

      从栈底到栈顶递增

      若栈为空,则入栈

      若栈不为空且待插入元素大于栈顶元素,则直接将待插入元素入栈

      若栈不为空且待插入元素小于栈顶元素,则将栈顶元素出栈,直到待插入元素大于栈顶元素

    然后反向遍历,采用单调递减栈(从栈底到栈顶递减),找出自始至终没有出栈的最大索引r,单调递减栈特性

      从栈底到栈顶递减

      栈为空,则入栈

      栈不为空且待插入元素小于栈顶元素,直接入栈

      若栈不为空且待插入元素大于栈顶元素,将栈顶元素出栈,直到待插入元素小于栈顶元素

 图解

正向遍历,单调递增栈

 

 逆向遍历,单调递减栈

 

  •  代码参考
 1 class Solution {
 2 public:
 3     int findUnsortedSubarray(vector<int>& nums) {
 4         stack<int> s;
 5         if(nums.empty())
 6             return 0;
 7         int len=nums.size();
 8         //首先正向遍历,单调递增栈,找出自始至终没有出栈的最小索引l
 9         /*
10         单调递增栈:从栈底到栈顶递增
11         栈为空,入栈
12         栈不为空且待插入元素大于栈顶元素,入栈
13         栈不为空且待插入元素小于栈顶元素,出栈,直到待插入元素大于栈顶元素
14         */
15         int l=len-1;
16         for(int i=0;i<len;++i)
17         {
18             while(!s.empty()&&nums[i]<nums[s.top()])
19             {
20                 l=min(l,s.top());
21                 s.pop();
22             }
23             s.push(i);
24         }
25         /*
26         然后逆向遍历,单调递减栈,找到自始至终没有出栈的最大索引r
27         单调递减栈:从栈底到栈顶递减
28         栈为空,入栈
29         栈不为空且待插入元素小于栈顶元素,入栈
30         栈不为空且待插入元素大于栈顶元素,出栈直到待插入元素小于栈顶元素
31         */
32         int r=0;
33         s=stack<int>();
34         for(int i=len-1;i>=0;--i)
35         {
36             while(!s.empty()&&nums[i]>nums[s.top()])
37             {
38                 r=max(r,s.top());
39                 s.pop();
40             }
41             s.push(i);
42         }
43         return (r>l)?(r-l+1):0;
44     }
45 };
  •  JAVA代码参考
 1 class Solution {
 2     public int findUnsortedSubarray(int[] nums) {
 3         /*
 4             要找到最短无序连续子数组,我们需要只需要找到子数组中开始和结束的索引位置即可
 5             首先开始正向扫描
 6             首先要找到开始时的索引位置,即找到最小的索引位置,需要维护一个变量l=len-1
 7             维护一个最大栈
 8             若栈为空,则直接将待插入元素入栈
 9             若栈非空且待插入元素大于栈顶元素,则直接将待插入元素入栈
10             若栈非空且待插入元素小于栈顶元素,则此时需要将栈顶元素出栈,且l=min(l,s.peek())
11             正向遍历结束后找到数组最小索引位置:l
12 
13             然后开始反向扫描
14             反向扫描的目的是找到最大的索引位置,需要维护一个变量r=0;
15             反向扫描,从尾到头遍历整个数组
16             维护一个最小栈
17             当栈为空时,直接将待插入元素入栈
18             当栈非空且待插入元素小于栈顶元素时,将待插入元素入栈
19             当栈非空且待插入元素大于栈顶元素时,需要将栈顶元素出栈,且r=max(r,s.peek())
20             反向遍历结束后找到数组最大索引位置r
21 
22             若r>0,则数组长度为r-l+1
23             否则,找不到这样的数组
24         */
25         //首先求取数组的长度
26         int len=nums.length;
27         //首先正向遍历,利用最大栈实现
28         Stack<Integer> maxstack=new Stack<Integer>();
29         //为了找到最短子数组的最小索引,维护一个变量l=len-1
30         int l=len-1;
31         for(int i=0;i<len;++i)
32         {
33             while(!maxstack.empty()&&nums[i]<nums[maxstack.peek()])
34             {
35                 l=Math.min(l,maxstack.peek());
36                 maxstack.pop();
37             }
38             maxstack.push(i);
39         }
40 
41         //然后开始反向扫描,为了找到子数组的最大索引,维护一个变量r=0;
42         Stack<Integer> minstack=new Stack<Integer>();
43         int r=0;
44         for(int i=len-1;i>=0;--i)
45         {
46             while(!minstack.empty()&&nums[i]>nums[minstack.peek()])
47             {
48                 r=Math.max(r,minstack.peek());
49                 minstack.pop();
50             }
51             minstack.push(i);
52         }
53         return r>0?(r-l+1):0;
54         
55     }
56 }

 

  5.5  接雨水

  

 

 

  •  问题分析

  我们可以使用栈来跟踪可能储水的最长的条形块,使用栈就可以依次遍历内完成计算

  我们在遍历数组是维护一个单调递减栈

    如果栈为空,则将数组元素入栈

    如果栈非空且待插入元素小于栈顶元素,将数组元素入栈

    如果栈非空且待插入元素大于栈顶元素,栈顶元素出栈,直到待插入元素小于栈顶元素。如果发现一个条形块长于栈顶,可以确定栈顶的条形状被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案到sum

  算法如下:

  遍历整个数组

    • 如果栈非空且待插入元素大于栈顶元素,即height[i]>hegiht[s.top()] 
      • 栈顶元素将被弹出,弹出元素即为t;
      • 计算当前元素和栈顶元素的距离,准备进行填充操作

          distance=i-s.top()-1;

      • 找出界定高度

          bound_height=min(height[i],height[s.top()])-height[top]

      • 累加储水量sum+=distance*bound_height
    • 如果当前栈为空或者待插入元素小于栈顶元素,直接将当前索引下标入栈
    • 遍历下一个数组元素

 

  • 代码参考
 1 class Solution {
 2 public:
 3     int trap(vector<int>& height) {
 4         if(height.empty())
 5             return 0;
 6         stack<int> s;
 7         int len=height.size();
 8         int sum=0;
 9         for(int i=0;i<len;++i)
10         {
11             while(!s.empty()&&height[i]>height[s.top()])
12             {
13                 int t=s.top();
14                 s.pop();
15                 if(s.empty())
16                     break;
17                 int distance=i-s.top()-1;
18                 int bound_height=min(height[i],height[s.top()])-height[t];
19                 sum+=distance*bound_height;
20             }
21             s.push(i);
22         }
23         return sum;
24     }
25 };
  •   JAVA代码参考
 1 class Solution {
 2     public int trap(int[] height) {
 3         /*
 4             接雨水问题,本质也是一个单调栈的问题,和我们之前做的有所不同的是,若相邻两个元素,其中间是不能蓄雨水的,且蓄雨水依靠于最低的那一块
 5             使用递减栈
 6             若当前栈为空,则直接将待插入元素入栈
 7             若当前栈非空且待插入元素小于栈顶元素,则待插入元素直接入栈
 8             若当前栈非空且待插入元素大于栈顶元素,则出栈,但需要保留栈顶元素,因为蓄雨水依靠于那一部分
 9         */
10         int len=height.length;
11         //申请一个栈
12         Stack<Integer> s=new Stack<Integer>();
13         int top=0;
14         int bound_height=0;
15         int distance=0;
16         int sum=0;
17         for(int i=0;i<len;++i)
18         {
19             while(!s.empty()&&height[i]>height[s.peek()])
20             {          
21                 top=s.peek();
22                 s.pop();
23                 if(s.empty())
24                     break;
25                 distance=i-s.peek()-1;
26                 bound_height=Math.min(height[i],height[s.peek()])-height[top];
27                 sum+=distance*bound_height;
28             }
29             s.push(i);
30         }
31         return sum;
32     }
33     
34 }

5.6 股票价格跨度

  

 

  • 题目分析

  由于要求出小于或等于今天价格的最大连续日数等价求出最近的一个大于今日价格的日子。可以使用单调栈来实现,最小栈维护两个栈,一个栈存储价格prices,一个栈存储天数weight

  存储价格的栈是最小栈

    若当前栈为空,则直接入栈

    若当前栈非空且待插入元素小于栈顶元素,则直接入栈

    若当前栈非空且待插入元素大于栈顶元素,则将栈顶元素出栈,并且当前权重值w+=weight.peek(),weight.pop()

  • C++代码参考
 1 class StockSpanner {
 2 public:
 3     /*
 4         要实现求股票的价格跨度,使用两个栈来实现
 5     */
 6     stack<int> prices;
 7     stack<int> weight;
 8     StockSpanner() {
 9         
10     }
11     
12     int next(int price) {
13         int w=1;
14         while(!prices.empty()&&price>=prices.top())
15         {
16             prices.pop();
17             w+=weight.top();
18             weight.pop();
19         }
20         prices.push(price);
21         weight.push(w);
22         return w;
23     }
24 };
25 
26 /**
27  * Your StockSpanner object will be instantiated and called as such:
28  * StockSpanner* obj = new StockSpanner();
29  * int param_1 = obj->next(price);
30  */

 

  • JAVA代码参考
 1 class StockSpanner {
 2 
 3     //使用两个栈来实现
 4     Stack<Integer> prices;
 5     Stack<Integer> weight;
 6     public StockSpanner() {
 7         prices=new Stack<Integer>();
 8         weight=new Stack<Integer>();
 9     }
10     
11     public int next(int price) {
12         int w=1;
13         /*
14             使用单调栈来实现
15             若当前栈为空,则直接入栈
16             若当前栈非空且待插入元素小于栈顶元素,直接将待插入元素入栈
17             若当前栈非空且待插入元素大于栈顶元素,则将当前元素出栈
18         */
19         while(!prices.empty()&&price>=prices.peek())
20         {
21             prices.pop();
22             w+=weight.peek();
23             weight.pop();
24         }
25         //否则,将待插入元素入栈
26         prices.push(price);
27         weight.push(w);
28         return w;
29     }
30 }
31 
32 /**
33  * Your StockSpanner object will be instantiated and called as such:
34  * StockSpanner obj = new StockSpanner();
35  * int param_1 = obj.next(price);
36  */

 

 

5.7 柱状图中最大的矩形

  

 

 

  •  问题分析

  使用单调递增栈+哨兵法来进行实现

  遍历的时候,记录下标,如果当前的高度比他之前的高度严格小于的时候,就可以直接确定之前的那个高的柱形的最大矩形面积

   用一个具体的例子来帮助理解[2,1,5,6,2,3]。我们需要求出每一根柱子的左侧且最近的小于其高度的柱子,初始时栈为空

    • 我们枚举2,因为栈为空,所以2左侧的柱子是【哨兵】,位置为-1,随后我们将2入栈
      • 栈:[2(0)],left[0]=-1(括号内的元素表示柱子在原数组中的位置)
    • 我们枚举1,此时栈不为空,但当前枚举元素1小于栈顶元素2,因此将元素1出栈,出栈后栈为空,此时我们将1入栈
      • 栈:[1(1)],left[1]=-1  
    • 我们枚举5,由于元素5大于元素1,因此不会移除栈顶元素,所以5左侧的柱子是1,位置为0,随后我们将元素5入栈
      • 栈:[1(1),5(2)],left[2]=1  
    • 我们枚举6,由于元素6大于元素5,因此不会移除栈顶元素,故6左侧的柱子是5,位置为2,随后我们将元素6入栈
      • 栈:[1(1),5(2),6(3)],left[3]=2
    • 我们枚举2,由于2<6,则将6出栈,此时栈顶为5,2<5,继续将元素5出栈,此时栈顶为1,故此时将2入栈
      • 栈:[1(1),2(4)],left[4]=1  
    • 我们枚举3,由于3>2,因此直接将3入栈
      • 栈:[1(1),2(4),3(5)],left[5]=4

  左边边界找到后,我们继续找右边哨兵,从最后一个元素开始反向遍历,需要求出每一根柱子的右侧最近的且小于其高度的柱子,初始栈为空

    • 我们枚举元素3,因为栈为空,所以3右侧的柱子是【哨兵】,位置为len,随后我们将3入栈
      • 栈:[3(5)],left[5]=6  
    • 我们枚举元素2,由于2<3,因此需要移除栈顶元素,移除栈顶元素后栈为空,此时将元素2入栈
      • 栈:[2(4)],left[4]=6  
    • 我们枚举元素6,由于元素6>2,因此直接将6入栈,且6右侧的柱子是【哨兵】,此时将元素6入栈
      • 栈[2(4),6(3)],left[3]=4  
    • 我们枚举元素5,由于5<6,因此需要移除栈顶元素6,移除后栈顶元素为2,位置为4
      • 栈:[2(4),5(2)],left[2]=4  
    • 我们枚举元素1,因为元素1<5,因此需要移除栈顶元素5,而后栈顶元素为2,1<2,因此移除栈顶元素2,此时栈为空,随后将1入栈 
      • 栈:[1(1)],left[1]=6  
    • 我们枚举元素2,因为2>1,因此无需移除栈顶元素,直接将元素2入栈
      • 栈:[1(1),2(0)],left[0]=1  

  这样,可以得出左侧柱子编号分别为:left=[-1,-1,1,2,1,4]

           右侧柱子编号分别为:right=[1,6,4,4,6,

 

 

  • C++代码参考
 1 class Solution {
 2 public:
 3     int largestRectangleArea(vector<int>& heights) {
 4         int len=heights.size();
 5         if(len==0)
 6             return 0;
 7         if(len==1)
 8             return heights[0];
 9         vector<int> left(len,0);
10         vector<int> right(len,0);
11         stack<int> s;
12         stack<int> s1;
13         int maxarea=0;
14         //首先正向遍历,找到每一根柱子的左侧最近的且小于其高度的柱子
15         /*
16             单调递增栈
17             从头到尾遍历整个数组
18             若栈为空,则直接入栈
19             若栈非空且待插入元素大于栈顶元素,则直接将待插入元素入栈
20             若栈非空且待插入元素小于栈顶元素,则将栈顶元素出栈
21         */
22         for(int i=0;i<len;++i)
23         {
24             while(!s.empty()&&heights[i]<=heights[s.top()])
25             {
26                 s.pop();
27             }
28             left[i]=s.empty()?-1:s.top();
29             s.push(i);
30         }
31         //反向遍历:找到每一根柱子右侧最近且小于其高度的柱子
32         for(int i=len-1;i>=0;--i)
33         {
34             while(!s1.empty()&&heights[i]<=heights[s1.top()])
35             {
36                 s1.pop();
37             }
38             right[i]=s1.empty()?len:s1.top();
39             s1.push(i);
40         }
41         //求出每个元素为高的矩形面积
42         for(int i=0;i<len;++i)
43         {
44             maxarea=max(maxarea,(right[i]-left[i]-1)*heights[i]);
45         }
46         return maxarea;
47     }
48 };

 

  •  JAVA代码参考
 1 class Solution {
 2     public int largestRectangleArea(int[] heights) {
 3         int len=heights.length;
 4         Stack<Integer> s=new Stack<Integer>();
 5         int []left=new int[len];
 6         int []right=new int[len];
 7         //首先从左往右遍历,找到左边哨兵
 8         //最大栈实现
 9         /*
10             从头到尾遍历整个数组
11             栈为空,则待插入元素入栈
12             栈非空且待插入元素大于栈顶元素,直接将待插入元素入栈
13             栈非空且待插入元素小于栈顶元素,则将栈顶元素出栈,直到待插入元素小于栈顶元素
14         */
15         int maxarea=0;
16         for(int i=0;i<len;++i)
17         {
18             while(!s.empty()&&heights[i]<=heights[s.peek()])
19             {
20                 s.pop();
21             }
22             left[i]=s.empty()?-1:s.peek();
23             s.push(i);
24         }
25         //然后反向遍历,找到右边哨兵
26         s.clear();
27         for(int i=len-1;i>=0;--i)
28         {
29             while(!s.empty()&&heights[i]<=heights[s.peek()])
30             {
31                 s.pop();
32             }
33             right[i]=s.empty()?len:s.peek();
34             s.push(i);
35         }
36         for(int i=0;i<len;++i)
37         {
38             maxarea=Math.max(maxarea,(right[i]-left[i]-1)*heights[i]);
39         }
40         return maxarea;
41     }
42 }

 

5.8 去除重复字母

  

 

  •  问题分析

  题目的要求总结为三点:

  要求一:要去重

  要求二:去重字符串中的字符顺序不能打乱s中字符的相对顺序

  要求三:在所有符合上一条要求的去重字符串中,字典序最小的作为最小结果

   如果只需要满足要求一和要求二,对于小写字符来说,可以用数组实现去重目的。首先用不二数组instack记录栈中的元素,达到去重的目的,此时栈中的元素是没有重复的。

  若需要满足字典序最小的要求,我们需要引入栈来实现,遍历整个字符串

    若当前栈为空,则直接将待插入元素入栈

    若当前元素非空且待插入元素大于栈顶元素,则将待插入元素入栈

    若当前元素非空且待插入元素小于栈顶元素,若后面不存在栈顶元素,则不管,若后面存在栈顶元素,则将栈顶元素出栈

  • C++代码参考
 1 class Solution {
 2 public:
 3     string removeDuplicateLetters(string s) {
 4         /*
 5             需要三个要求
 6             1. 去重
 7             2. 保留目前字符相对顺序
 8             3. 返回结果的字典序最小
 9 
10             要实现去重,可以用数组来实现去重的目的
11             要实现字典序最小,可以用栈来实现
12             若当前栈为空,则直接将待插入元素入栈
13             若当前栈非空且待插入元素大于栈顶元素,则直接将待插入元素入栈
14             若当前栈非空且待插入元素小于栈顶元素,若栈顶在后面不存在,则保留,否则,将栈顶元素弹出
15         */
16         stack<char> st;
17         //首先实现去重
18         int count[256]={0};
19         for(int i=0;i<s.size();++i)
20         {
21             count[s[i]]++;
22         }
23         bool instack[256]={false};
24         for(int i=0;i<s.size();++i)
25         {
26             count[s[i]]--;
27             if(instack[s[i]])
28                 continue;
29             while(!st.empty()&&s[i]<st.top())
30             {
31                 if(count[st.top()]==0)
32                     break;
33                 //若之后不存在栈顶元素了,则直接停止pop
34                 instack[st.top()]=false;
35                 st.pop();
36             }
37             st.push(s[i]);
38             instack[s[i]]=true;
39         }
40         //已经完成了,此时栈内元素就是我们需要的元素
41         string sb="";
42         while(!st.empty())
43         {
44             sb=st.top()+sb;
45             st.pop();
46         }
47         return sb;
48     }
49 };
  • JAVA代码参考
 1 class Solution {
 2     public String removeDuplicateLetters(String s) {
 3         /*
 4             栈为空,则直接将待插入元素入栈
 5             栈非空且待插入元素大于栈顶元素,怎直接将待插入元素入栈
 6             栈非空且待插入元素小于栈顶元素,若后面还有栈顶元素吗,则将栈顶元素出栈
 7 
 8             因此,需要一个数组来记录是否有相关字符
 9         */
10         Stack<Character> st=new Stack<Character>();
11         char [] str=s.toCharArray();
12         //首先记录相关字符出现的次数
13         int[] count=new int[256];
14         for(int i=0;i<str.length;++i)
15         {
16             count[str[i]]++;
17         }
18         //采用一个bool数组来记录当前元素是否在栈中
19         boolean[] instack=new boolean[256];
20         for(char c:s.toCharArray())
21         {
22             count[c]--;
23             //如果当前栈中有该字符,则直接继续遍历下一个字符
24             if(instack[c])
25                 continue;
26             /*
27                 栈为空,则直接将待插入元素入栈
28                 栈非空且待插入元素大于栈顶元素,怎直接将待插入元素入栈
29                 栈非空且待插入元素小于栈顶元素,若后面还有栈顶元素,则将栈顶元素出栈
30             */
31             while(!st.empty() && c<st.peek())
32             {
33                 //如果之后不存在栈顶元素,则停止pop
34                 if(count[st.peek()]==0)
35                     break;
36                 //若后面还有相关字符,则停止pop
37                 instack[st.peek()]=false;
38                 st.pop();
39             }
40             st.push(c);
41             instack[c]=true;
42         }
43         //当成功之后,设置一个字符sb来装其他字符
44         StringBuilder sb=new StringBuilder();
45         while(!st.empty())
46         {
47             sb.append(st.pop());
48         }
49         return sb.reverse().toString();
50     }
51 }

 

5.9 不同字符的最小子序列

  

 

 

  •  问题分析

 题目的要求总结为三点:

  要求一:要去重

  要求二:去重字符串中的字符顺序不能打乱s中字符的相对顺序

  要求三:在所有符合上一条要求的去重字符串中,字典序最小的作为最小结果

   如果只需要满足要求一和要求二,对于小写字符来说,可以用数组实现去重目的。首先用不二数组instack记录栈中的元素,达到去重的目的,此时栈中的元素是没有重复的。

  若需要满足字典序最小的要求,我们需要引入栈来实现,遍历整个字符串

    若当前栈为空,则直接将待插入元素入栈

    若当前元素非空且待插入元素大于栈顶元素,则将待插入元素入栈

    若当前元素非空且待插入元素小于栈顶元素,若后面不存在栈顶元素,则不管,若后面存在栈顶元素,则将栈顶元素出栈

  • JAVA代码参考
 1 class Solution {
 2     public String smallestSubsequence(String s) {
 3         /*
 4             三个要求:
 5             1.去重
 6             2.保留原有顺序
 7             3.返回字典序最小的子序列
 8 
 9             要实现去重,由于都是小写英文字母,因此可以用数组来进行去重
10             可以用栈来实现返回字典序最小的子序列
11             若当前栈为空,则直接插入当前元素
12             若当前栈非空且待插入元素大于栈顶元素,则直接将待插入元素入栈
13             若当前栈非空且待插入元素小于栈顶元素,若栈顶元素在后面没有出现,则不出栈,若栈顶元素在后面出现了,则将栈顶元素出栈
14         */
15         Stack<Character> st=new Stack<Character>();
16         int[] count=new int[256];
17         char[] str=s.toCharArray();
18         for(int i=0;i<s.length();++i)
19         {
20             count[str[i]]++;
21         }
22         boolean[] instack=new boolean[256];
23         for(char c:s.toCharArray())
24         {
25             count[c]--;
26             //如果当前栈顶元素,如果当前元素在后面存在,则继续
27             if(instack[c])
28                 continue;
29             //实现返回字典最小的子序列
30             while(!st.empty()&&c<st.peek())
31             {
32                 //如果之后不存在栈顶元素了,则停止pop
33                 if(count[st.peek()]==0)
34                     break;
35                 instack[st.peek()]=false;
36                 st.pop();        
37             }
38             st.push(c);
39             instack[c]=true;
40         }
41         //此时已经完成了,栈内的元素就是我们需要的元素
42         StringBuilder sb=new StringBuilder();
43         while(!st.empty())
44         {
45             sb.append(st.peek());
46             st.pop();
47         }
48         return sb.reverse().toString();
49 
50     }
51 }

 

 5.10 移掉K位数字

  

 

  •  问题分析

  由于我们需要删除掉K位数字得到最小值,那么我们需要注意的是,删除的数字尽量在高位,则当前位小于前一位时,则前一位出栈,当前位入栈

 

  使用栈来实现

  对于123456这种类型的数字,是不能删除前面元素的,否则会越来越大

  对于54321这种类型的数字,删除前面的543则会使得留下的数字最小

  借助栈来实现,从头到尾扫描字符串中的每个元素

  若当前栈为空,则将待插入元素入栈

  若当前栈非空且待插入元素大于栈顶元素,则直接将待插入元素入栈,但若当前栈为空且待插入元素为0.则不插入

  若当前栈非空且待插入元素小于栈顶元素,将栈顶元素弹出,--k,当达到满足要求的k时,直接退出即可

  • JAVA代码参考

  

 1 class Solution {
 2     public String removeKdigits(String num, int k) {
 3         /*
 4             注意,移除k位数字,可以不是连续的
 5             对于123456这种类型的数字,是不能删除前面元素的,否则会越来越大
 6             对于54321这种类型的数字,删除前面的543则会使得留下的数字最小
 7 
 8             因此我们可以借助栈来实现,从头到尾扫描字符串中每个字符
 9             若当前栈为空,则将待插入元素入栈
10             若当前栈非空且待插入元素大于栈顶元素,则将待插入元素插入,但若栈底元素是0可以不插入
11             若当前栈非空且待插入元素小于栈顶元素,则将栈顶元素弹出,且k++,当达到满足要求的k时,则直接退出即可
12         */
13         //特殊情况全部删除
14         if(num.length()==k)
15             return "0";
16         Stack<Character> st=new Stack<Character>();
17         char[] str=num.toCharArray();
18         for(char c:num.toCharArray())
19         {
20             while(!st.empty()&&c<st.peek()&&k>0)
21             {
22                 
23                 st.pop();
24                 --k;
25             }
26             if(st.empty()&&c=='0')
27                 continue;
28             st.push(c);
29         }
30         //如果此时k还并未等于0,则说明可能出现类似12345的情形,因此需要将最栈顶的k位元素弹出即可
31         while(k>0)
32         {
33             st.pop();
34             k--;
35         }
36         //如果最后栈为空时,删除1一位,比如我们的10,删除一位为0,按照上面逻辑会返回"",因此我们让其返回0
37         if(st.empty())
38             return "0";
39         StringBuilder sb=new StringBuilder();
40         while(!st.empty())
41         {
42             sb.append(st.pop());
43         }
44         return sb.reverse().toString();
45     }
46 }

 

 6. 滑动窗口的最大值:栈和队列的对比

  6.1 滑动窗口的最大值

  

 

  •  问题分析

  将滑动窗口看成一个队列,窗口滑动时,处于窗口第一个数字被删除,同时在窗口的末尾添加一个新的数字,符合队列先进先出的原则。因此采用队列来存储滑动窗口。但我们不用队列来存储滑动窗口的所有值,而是存储滑动窗口中可能最大的值。从头到尾扫描整个数组,如果当前数组元素大于滑动窗口队列中的元素,则队列中的元素不可能成为滑动窗口的最大值,将其从队尾删除;如果当前数组元素小于滑动窗口队列中的元素,则当元素从滑动窗口队列头删除后,其可能成为滑动窗口最大元素,因此将元素入

 

 

  • C++ 代码参考
 1 class Solution {
 2 public:
 3     vector<int> maxInWindows(const vector<int>& num, unsigned int size)
 4     {
 5         /*
 6         为了求得滑动窗口最大值,利用队列来实现,并不将滑动窗口的每个数字存入队列,
 7         而是只把可能成为滑动窗口最大值的存入队列
 8         */
 9         vector<int> B;
10         if(num.size()>=size&&size>=1)
11         {
12             deque<int> index;
13             int len=num.size();
14             //首先是在滑动窗口中元素小于滑动窗口尺寸时,此时不需要判断是否滑动窗口中元素个数小于滑动窗口尺寸
15             for(int i=0;i<size;++i)
16             {
17                 while(!index.empty()&&num[i]>num[index.back()])
18                       index.pop_back();
19                 index.push_back(i);
20             }
21             //当滑动窗口元素大于窗口尺寸时,此时还需要判断滑动窗口中元素个数是否小于滑动窗口尺寸,若不小于,则还需要将滑动窗口队头元素删除
22             for(int i=size;i<len;++i)
23             {
24                 B.push_back(num[index.front()]);
25                 while(!index.empty()&&num[i]>num[index.back()])
26                     index.pop_back();
27                 while(!index.empty()&&index.front()<=(int)(i-size))
28                     index.pop_front();
29                 index.push_back(i);
30             }
31             B.push_back(num[index.front()]);
32         }
33         return B;
34     }
35 };
  •  JAVA代码参考
 1 class Solution {
 2     public int[] maxSlidingWindow(int[] nums, int k) {
 3         /*
 4             使用队列来实现
 5             若当前队列为空,则直接将元素入队列
 6             若当前队列非空且待插入元素小于栈顶元素,则直接将待插入元素入栈,若队列中元素大于3,则将队头元素移除
 7             若当前队列非空且待插入元素大于栈顶元素,则将队尾元素移除后将待插入元素插入
 8         */
 9         int len=nums.length;
10         if(nums.length == 0 || k == 0) 
11             return new int[0];
12         //List<Integer> list=new ArrayList<Integer>();
13         int[] B=new int[len-k+1];
14         int j=0;
15         //从头到尾遍历整个数组
16         if(len>=k&&k>=1)
17         {
18             //使用双端队列来实现
19             Deque<Integer> deque=new LinkedList<Integer>();
20             //当滑动窗口的内的元素个数小于滑动窗口的大小时,此时不需要考虑队列中的元素长度大于k的情况
21             for(int i=0;i<k;++i)
22             {
23                 while(!deque.isEmpty()&&nums[i]>nums[deque.peekLast()])
24                 {
25                     deque.pollLast();
26                 }
27                 deque.addLast(i);
28             }
29             //当滑动窗口内的元素个数刚好等于滑动窗口的大小时,此时还需要考虑队列中的元素个数是否大于滑动窗口的大小,若队列中元素个数大于滑动窗口的大小,则需要将队头元素移除
30             for(int i=k;i<len;++i)
31             {
32                 B[j++]=nums[deque.getFirst()];
33                 while(!deque.isEmpty()&&nums[i]>nums[deque.getLast()])
34                 {
35                     deque.pollLast();
36                 }
37                 while(!deque.isEmpty()&&(i-(int)deque.getFirst())>=k)
38                     deque.pollFirst();
39                 deque.addLast(i);
40             }
41             B[j++]=nums[deque.getFirst()];
42         }
43         return B;
44         
45     }
46 }

 

 

  6.2 滑动窗口的最大值

  

 

  •  题目描述

  为了解决上述问题,即定义一个函数得到队列中的最大值,则其可以参考上述滑动窗口的最大值来解决问题,刚开始可能思考,直接定义一个队列,每次入队操作时更新其最大值

 

 

 但是出队后,这个方法会造成信息丢失,即当最大值出队后,我们无法知道队列里的下一个最大值

为了解决上述问题,我们只需记住当前最大值出队后,队列中的下一个最大值即可

具体方法是使用一个双端队列dequeue,在每次入队时。如果dequeue队尾元素小于即将入队的元素,则将小于value的全部元素出队后,再将value入队,否则直接入队

 

 

 

  •  代码参考

  

 1 class MaxQueue {
 2 public:
 3     queue<int> data;
 4     deque<int> maxinums;
 5     MaxQueue() {
 6 
 7     }
 8     
 9     int max_value() {
10         if(maxinums.empty())
11             return -1;
12         return maxinums.front();
13     }
14     
15     void push_back(int value) {
16         while(!maxinums.empty()&&value>=maxinums.back())
17             maxinums.pop_back();
18         data.push(value);
19         maxinums.push_back(value);
20     }
21     
22     int pop_front() {
23         if(data.empty())
24             return -1;
25         int ans=data.front();
26         if(ans==maxinums.front())
27             maxinums.pop_front();
28         data.pop();
29         return ans;
30     }
31 
32 };
33 
34 /**
35  * Your MaxQueue object will be instantiated and called as such:
36  * MaxQueue* obj = new MaxQueue();
37  * int param_1 = obj->max_value();
38  * obj->push_back(value);
39  * int param_3 = obj->pop_front();
40  */
  •  JAVA代码参考
 1 class MaxQueue {
 2     //要想得到队列中的最大值,我们可以使用如下方式
 3     //使用两个队列实现,主队列正常压入元素,辅助队列采用最小队列
 4     /*
 5         若队列为空,直接将待插入元素入队列
 6         若队列非空且待插入元素小于队尾元素,则将待插入元素入队列尾
 7         若队列为空且待插入元素大于队尾元素,则将队尾元素删除
 8     */
 9     //主队列为单队列
10     Queue<Integer> queue;
11     Deque<Integer> deque;
12     public MaxQueue() {
13         queue=new LinkedList<Integer>();
14         deque=new LinkedList<Integer>();
15     }
16     
17     public int max_value() {
18         if(deque.isEmpty()) 
19             return -1;
20         return deque.peekFirst();
21     }
22     
23     public void push_back(int value) {
24         while(!deque.isEmpty()&&value>=deque.peekLast())
25             deque.pollLast();
26         queue.offer(value);
27         deque.offerLast(value);
28         
29     }
30     
31     public int pop_front() {
32         if(queue.isEmpty())
33             return -1;
34         int ans=queue.poll();
35         if(ans==deque.peekFirst())
36             deque.pollFirst();
37         return ans;
38     }
39 }
40 
41 /**
42  * Your MaxQueue object will be instantiated and called as such:
43  * MaxQueue obj = new MaxQueue();
44  * int param_1 = obj.max_value();
45  * obj.push_back(value);
46  * int param_3 = obj.pop_front();
47  */

 

 

 7. 验证栈序列

      

 

  • 问题分析

  要判断序列是否是栈的弹出序列,可以借助辅助栈来进行实现

  定义一个辅助栈

  遍历pushed数组

    若栈为空,则将pushed数组中元素入栈

    若栈不为空且栈顶元素不等于popped第一个元素,则入栈

    若栈不为空且栈顶元素等于popped第一个元素,则将栈顶元素出栈

 

 

  • 代码参考
 1 class Solution {
 2 public:
 3     bool validateStackSequences(vector<int>& pushed, vector<int>& popped)    {
 4         stack<int> s;
 5         int len=pushed.size();
 6         //栈为空,入栈
 7         //栈不为空且弹出序列不等于栈顶元素,入栈
 8         //栈不为空且弹出序列等于栈顶元素,将栈顶元素出栈
 9         int j=0;
10         for(int i=0;i<len;++i)
11         {
12             s.push(pushed[i]);
13             while(!s.empty()&&s.top()==popped[j])
14             {
15                 s.pop();
16                 ++j;
17             }
18         }
19         return s.empty();
20     }
21 };
  •  JAVA代码参考
 1 class Solution {
 2     public boolean validateStackSequences(int[] pushed, int[] popped) {
 3         int len=pushed.length;
 4         //若压栈序列和出栈序列长度不相同,则其为假
 5         if(pushed.length!=popped.length)
 6             return false;
 7         int j=0;
 8         Stack<Integer> s=new Stack<Integer>();
 9         //从头到尾遍历整个压栈序列
10         for(int i=0;i<len;++i)
11         {
12             s.push(pushed[i]);
13             while(!s.empty()&&popped[j]==s.peek())
14             {
15                 s.pop();
16                 ++j;
17             }
18             
19         }
20         return s.empty();
21     }
22 }

 

8. 删除最外层的括号

  

 

  •  问题分析

  对于成对括号的问题,一般都会思考使用栈来进行匹配

  当待插入字符为左括号"("时,可直接将字符入栈

  当待插入字符为左括号")"时,将栈顶元素出栈

  若出栈后栈为空,则此左括号是最外层左括号,可以直接删除

  若入栈前栈为空,则此有括号是最外层有括号,可以直接删除

  • C++代码参考
 1 class Solution {
 2 public:
 3     string removeOuterParentheses(string S) {
 4         /*
 5             若当前字符为左边括号,则直接入栈
 6             若当前字符为右边括号,则直接出栈
 7             若出栈后栈为空,则此右边括号是最外层括号
 8             若入栈前栈为空,则此左括号是最外层括号
 9         */
10         stack<string> s;
11         string mystring;
12         for(int i=0;i<S.size();++i)
13         {
14             //如果当前字符为左边字符且栈为空
15             if(s.empty()&&S[i]=='(')
16             {
17                 s.push("(");
18             }
19             else if(!s.empty()&&S[i]=='(')
20             {
21                 mystring+=S[i];
22                 s.push("(");
23             }
24             if(S[i]==')')
25             {
26                 s.pop();
27                 if(!s.empty())
28                     mystring+=S[i];
29             }
30         }
31         return mystring;
32     }
33 };

 

  • JAVA代码参考
 1 class Solution {
 2     public String removeOuterParentheses(String S) {
 3         /*
 4             可以借助栈来实现
 5             碰到'('则入栈,碰到')'则出栈
 6             若栈为空,则刚刚碰到的')'是最外层右括号
 7             若入栈之前栈为空,则即将入栈的'('就是最外层括号
 8             可以使用一个新的字符串来实现,为了节省空间,可以使用StringBuilder
 9         */
10         Stack<String> s=new Stack<String>();
11         StringBuilder sb=new StringBuilder();
12         for(char c:S.toCharArray())
13         {
14             //如果栈为空并且入栈的元素是'(',则此时的'('即为最左边括号
15             if(s.isEmpty()&&c=='(')
16             {
17                 s.push("(");
18             }
19             //如果当前栈非空且入栈的元素是'(',则其不是最左边括号,不需要删除
20             else if(!s.isEmpty()&&c=='(')
21             {
22                 sb.append(c);
23                 s.push("(");
24             }
25             //如果当前栈为右半边括号,则其应该出栈
26             if(c==')')
27             {
28                 s.pop();
29                 //如果此时栈为空,则其为最左边括号
30                 if(!s.isEmpty())
31                 {
32                     sb.append(c);
33                 }
34             }
35         }
36         return sb.toString();
37     }
38 }

 

9. 简化路径

  

 

 

 问题分析

  分析题目可知,规范的路径包含以下要点:

    • 必须以/开头
    • 两个目录名之间必须只有一个/
    • 最后一个目录名不能以/结尾

  由于遇到"."表示目录本身,".."表示将目录切换到上级目录,因此用栈来表示,我们可以采用以下步骤

  为了实现简化路径的需求,由于路径都是以"/"分割的,因此我们可以将路径指代的字符串转换为只由路径组成的字符串数组

  1. 首先将路径字符串以"/"分割成字符串数组

  2. 若当前字符串为".",则表示本身,继续遍历下一个元素即可

  3. 若当前字符串为"..",表示切换到上级目录,若栈非空,则将栈顶元素出栈

  4. 否则,说明当前字符串是路径信息,直接入栈即可

  当遍历完整个字符串,有用的路径信息都在栈中,采用将字符串往前加的形式即可得到简化后的路径

  • JAVA代码参考
 1 class Solution {
 2     public String simplifyPath(String path) {
 3         /*
 4             规范的路径几种情况
 5             1. 规范的路径必须始终以斜杠/开头
 6             两个目录名之间必须只有一个斜杠/
 7             最后一个目录名不能以/结尾
 8 
 9             为了实现简化路径的需求,由于路径都是以"/"分割的,因此我们可以将路径指代的字符串转化为只由路径组成的字符串数组
10             1. 首先将路径字符串以"/"分割成字符串数组
11             2. 若当前字符串为".",则遍历到本家,继续遍历下一个元素即可
12             2. 遍历整个字符串数组,若当前字符串为"..",则出栈,但是需要栈非空
13             3. 若当前字符串不为"..",则说明元素是路径信息,直接入栈
14             当遍历完整个字符串中,有用的路径信息都在栈中,采用将字符串往前加的形式即可得到简化后的路径
15         */
16         Stack<String> s=new Stack<String>();
17         String sb="";
18         String[] str=path.split("/");
19         for(int i=0;i<str.length;++i)
20         {
21             String cur=str[i].trim();//去掉多余空格
22             //若当前元素为"."
23             if(cur==null||cur.length()==0||cur.equals("."))
24                 continue;
25             //若当前元素为"..",在栈不为空的前提下将栈顶元素出栈
26             if(cur.equals(".."))
27             {
28                 if(!s.empty())
29                     s.pop();
30             }
31             //否则,元素是路径信息,直接入栈
32             else
33                 s.push(cur);
34         }
35         //遍历完后,所有路径信息都保存在栈中
36         while(!s.empty())
37         {
38             sb="/"+s.peek()+sb;
39             s.pop();
40         }
41         return (sb.length()==0)?"/":sb;
42     }
43 }

 

10. 删除字符串中的所有相邻重复项

  

 

  •  问题分析

  可以用栈来实现

  设计一个栈,其插入弹出按照如下规律实现:遍历字符串中的所有字符

    • 若当前栈为空,则将当前字符入栈
    • 若当前栈非空且待插入元素不等于栈顶元素,则将待插入元素入栈
    • 若当前栈非空且待插入元素等于栈顶元素,则将栈顶元素弹出,遍历下一个元素
  • C++代码参考

   

 1 class Solution {
 2 public:
 3     string removeDuplicates(string S) {
 4         /*
 5             若当前栈为空,则将待插入元素入栈
 6             若当前栈非空且待插入元素不等于栈顶元素,则将待插入元素入栈
 7             若当前栈非空且带插入元素等于栈顶元素,则将栈顶元素出栈,继续遍历下一个元素
 8         */
 9         if(S.empty())
10             return "";
11         stack<char> st;
12         for(int i=0;i<S.size();++i)
13         {
14             if(!st.empty()&&S[i]==st.top())
15             {
16                 st.pop();
17             }
18             else
19                 st.push(S[i]);
20         }
21         string res="";
22         while(!st.empty())
23         {
24             res+=st.top();
25             st.pop();
26         }
27         reverse(res.begin(),res.end());
28         return res;
29     }
30 };
  • JAVA代码参考
 1 class Solution {
 2     public String removeDuplicates(String S) {
 3         /*
 4             若栈为空,则直接将待插入字符入栈
 5             若栈非空且待插入元素不等于栈顶元素,将待插入元素入栈
 6             若栈非空且待插入元素等于栈顶元素,将栈顶元素出栈
 7         */
 8         if(S.length()==0)
 9             return "";
10         Stack<Character> st=new Stack<Character>();
11         char[] str=S.toCharArray();
12         for(int i=0;i<str.length;++i)
13         {
14             if(!st.empty()&&str[i]==st.peek())
15             {
16                 st.pop();
17             }
18             else
19                 st.push(str[i]);
20         }
21         StringBuilder sb=new StringBuilder();
22         while(!st.empty())
23         {
24             sb.append(st.pop());
25         }
26         return sb.reverse().toString();
27     }
28 }

 

10.2 删除字符串中的所有相邻重复项2

  

 

  •  问题分析

  若当前字符与前一个字符不同时,往栈中压入1

  否则,栈顶元素+1

  如果栈顶元素等于k,则从字符中删除这K个字符,并将k从栈顶移除

  • JAVA代码参考

  

 1 class Solution {
 2     public String removeDuplicates(String s, int k) {
 3         /*
 4             算法:
 5                 当前字符与前一个不同时,往栈中压入1
 6                 否则,栈顶元素+1·
 7                 如果栈顶元素等于k,则从字符串中删除这k个字符,并将k从栈顶移除
 8         */
 9         Stack<Integer> st=new Stack<Integer>();
10         StringBuilder sb=new StringBuilder(s);
11         for(int i=0;i<sb.length();++i)
12         {
13             //如果当前字符与前一个字符不同时,向栈中压入1
14             if(i==0||sb.charAt(i)!=sb.charAt(i-1))
15             {
16                 st.push(1);
17             }
18             //否则,将栈顶元素+1
19             else
20             {
21                 int incremented=st.pop()+1;
22                 if(incremented==k)
23                 {
24                     sb.delete(i-k+1,i+1);
25                     i=i-k;
26                 }
27                 else
28                     st.push(incremented);
29             }
30         }
31         return sb.toString();
32     }
33 }

 

 

11. 用栈构建其他数据结构

11.1 用栈操作构建数组

  

 

  •  问题分析

  用栈来实现

  首先,看到这个问题得到时候,我们需要明确的一点是,哪个对应的变量应该被存储在栈中。通过观察问题,target数组中存放的元素是1-n之间的数,所以为了遍历谁被存入target中,我们需要从1-n的角度去遍历,因为target中存储的数没有完全包含这n个数。所以这里我们就应该清楚了,栈中的元素是1-n(可由理解为1-n的以倒序的方式存储在栈中)

  然后我们通过一次遍历栈顶元素吗,然后对比栈顶元素与target数组中的值,如果栈顶元素等于target当前访问的下标所对应的值,则仅将"Push"存入结果中,如果栈顶元素不等于target当前访问的下标所对应的值,则需要依次将"Push"和"Pop"存入栈中

  • C++代码参考

  

 1 class Solution {
 2 public:
 3     vector<string> buildArray(vector<int>& target, int n) {
 4         vector<string> vt;
 5         stack<int> s;
 6         int index=0;
 7         for(int i=1;i<=n;++i)
 8         {
 9             if(index<target.size())
10             {
11                 s.push(i);
12                 vt.push_back("Push");
13             }
14             if(index<target.size()&&s.top()!=target[index])
15             {
16                 s.pop();
17                 vt.push_back("Pop");
18             }
19             else
20                 ++index;
21         }
22         return vt;
23     }
24 };
  • JAVA代码参考

  

 1 class Solution {
 2     public List<String> buildArray(int[] target, int n) {
 3         Stack<Integer> st=new Stack<Integer>();
 4         List<String> list=new ArrayList<String>();
 5         int index=0;
 6         for(int i=1;i<=n;++i)
 7         {
 8             if(index<target.length)
 9             {
10                 st.push(i);
11                 list.add("Push");
12             }
13             if(index<target.length&&st.peek()!=target[index])
14             {
15                 st.pop();
16                 list.add("Pop");
17             }
18             else
19                 ++index;
20         }
21         return list;
22 
23     }
24 }

 

11.2 化队为栈

  

 

  • 问题分析

  栈的特点:先入后出

  队列的特点:先入先出

  可以使用两个栈来实现:一个数据栈,一个辅助栈

    若当前数据栈为空,则可以直接向数据栈中压入数据

    若当前数据栈非空,则先向数据栈中的元素压入辅助栈,再将待插入元素入栈,再将辅助栈中所有元素压入数据栈

  • C++代码参考
 1 class MyQueue {
 2 public:
 3     /*
 4         使用两个栈来实现,一个数据栈,一个辅助栈
 5         若数据栈为空,直接插入待插入数据
 6         若数据栈非空,则将数据栈中的数据插入辅助栈后,将待插入数据插入后,将辅助栈中的所有元素压入数据栈
 7     */
 8     stack<int> datastack;
 9     stack<int> helpstack;
10     /** Initialize your data structure here. */
11     MyQueue() {
12 
13     }
14     
15     /** Push element x to the back of queue. */
16     void push(int x) {
17         while(!datastack.empty())
18         {
19             helpstack.push(datastack.top());
20             datastack.pop();
21         }
22         datastack.push(x);
23         while(!helpstack.empty())
24         {
25             datastack.push(helpstack.top());
26             helpstack.pop();
27         }
28     }
29     
30     /** Removes the element from in front of queue and returns that element. */
31     int pop() {
32         int ret = datastack.top();
33         datastack.pop();
34         return ret;
35     }
36     
37     /** Get the front element. */
38     int peek() {
39         return datastack.top();
40     }
41     
42     /** Returns whether the queue is empty. */
43     bool empty() {
44         return datastack.empty();
45     }
46 };
47 
48 /**
49  * Your MyQueue object will be instantiated and called as such:
50  * MyQueue* obj = new MyQueue();
51  * obj->push(x);
52  * int param_2 = obj->pop();
53  * int param_3 = obj->peek();
54  * bool param_4 = obj->empty();
55  */
  • JAVA代码参考

  

 1 class MyQueue {
 2 
 3     //栈的特点:先入后出
 4     //队列的特点:先进先出
 5     /*
 6         可以使用两个栈来实现:一个数据栈,一个辅助栈
 7             若当前数据栈为空,则可以直接向数据栈中压入数据
 8             若当前数据栈非空,则先将数据栈中的所有元素压入辅助栈,再将待插入元素入栈,再将辅助栈中所有元素压入数据栈
 9     */
10     Stack<Integer> datastack;
11     Stack<Integer> helpstack;
12     /** Initialize your data structure here. */
13     public MyQueue() {
14         datastack=new Stack<Integer>();
15         helpstack=new Stack<Integer>();
16     }
17     
18     /** Push element x to the back of queue. */
19     public void push(int x) {
20         while(!datastack.empty())
21         {
22             helpstack.push(datastack.pop());
23         }
24         datastack.push(x);
25         while(!helpstack.empty())
26             datastack.push(helpstack.pop());
27     }
28     
29     /** Removes the element from in front of queue and returns that element. */
30     public int pop() {
31         return datastack.pop();
32     }
33     
34     /** Get the front element. */
35     public int peek() {
36         return datastack.peek();
37     }
38     
39     /** Returns whether the queue is empty. */
40     public boolean empty() {
41         return datastack.empty();
42     }
43 }
44 
45 /**
46  * Your MyQueue object will be instantiated and called as such:
47  * MyQueue obj = new MyQueue();
48  * obj.push(x);
49  * int param_2 = obj.pop();
50  * int param_3 = obj.peek();
51  * boolean param_4 = obj.empty();
52  */

 

12. 整理字符串

  

 

  •  问题分析

  从头遍历字符串,若当前字符是栈顶元素对应的大小写,则将栈顶元素出栈

  • JAVA代码参考

  

 1 class Solution {
 2     public String makeGood(String s) {
 3         //实现思路:从头遍历字符串,依次将字符串中的字符入栈,若当前字符是栈顶字符对应的大小写,则将栈顶元素依次弹出即可
 4         if(s.length()==0||s.length()==1)
 5             return s;
 6         Stack<Character> st=new Stack<Character>();
 7         char[] str=s.toCharArray();
 8         for(int i=0;i<str.length;++i)
 9         {
10             //如果栈为空,则直接将元素压栈即可
11             if(st.empty())
12             {
13                 st.push(str[i]);
14                 continue;
15             } 
16             //如果当前字符是栈顶字符对应的大小写,则将栈顶元素依次弹出即可
17             if(str[i]-st.peek()==32||str[i]-st.peek()==-32)
18             {
19                 st.pop();
20             }
21             else
22                 st.push(str[i]);
23         }
24         //然后将栈中字符反转后转换为字符串即可
25         StringBuilder sb=new StringBuilder();
26         while(!st.empty())
27         {
28             sb.append(st.pop());
29         }
30         return sb.reverse().toString();
31     }
32 }

 

13. 行星碰撞

  

 

  •  问题分析

  使用栈来实现

  如果不会发生碰撞,那么一小排行星是处于稳定的状态。若在右边增加一个新的小行星后,在它再次稳定之前,可能会发生更多的碰撞,而所有的这些碰撞(如果发生的话)都必须从右到左发生。这种情况非常适合栈解决

  算法:

    假设栈中顶部元素为top,一个新的小行星new进来了,如果new向右移动(new>0),或者top向左移动(top<0),则不会发生碰撞。

    否则,如果abs(new)<abs(top),则新小行星new将爆炸;如果abs(new)=abs(top),则两个小行星都将爆炸;如果abs(new)>abs(top),则top小行星将爆炸(可能还会有更多小行星爆炸,因此我们应继续检查)

  • JAVA代码参考
 1 class Solution {
 2     public int[] asteroidCollision(int[] asteroids) {
 3         /*
 4             行星碰撞,数字代表大小,正负代表行星的移动方向,使用栈来实现
 5             如果不会发生碰撞那么一排小行星是处于稳定的状态。若在右边增加一个新的小行星之后,在它再次稳定之前,可能会发生更多的碰撞,而所有的这些碰撞(如果发生的话)都必须从右到左。这种情况非常适合用栈来解决
 6             算法:
 7                 假设栈中顶部元素为top,一个小行星new进来了。如果new向右移动(new>0),或者top向左移动(top<0),则不会发生碰撞
 8                 否则,
 9                     如果abs(new)<abs(top),则小行星new将爆炸
10                     如果abs(new)==abs(top),则两个小行星都将爆炸
11                     如果abs(new)>abs(top),则小行星top将爆炸
12 
13         */
14         Stack<Integer> st=new Stack<Integer>();
15         
16         for(int newstar:asteroids)
17         {
18             collision:{
19                 while(!st.empty()&newstar<0&&st.peek()>0)
20                 {
21                     if(st.peek()<-newstar)
22                     {
23                         st.pop();
24                         continue;
25                     }
26                     else if(st.peek()==-newstar)
27                         st.pop();
28                     break collision;          
29                 }
30                 st.push(newstar);
31                 
32             }
33         }
34         int j=0;
35         int len=st.size();
36         int[] output=new int[len];
37         while(!st.empty())
38         {
39             output[len-j-1]=st.pop();
40             ++j;
41         }
42         return output;
43         
44     }
45 }

 

 14 反转每对括号间的子串

  

 

  •  问题分析

  使用栈来存储左边括号的索引

  若当前字符为左边括号(,则将左边括号的索引入栈

  若当前字符为右边括号),则将两个索引之间的字符串翻转

  最后浏览一遍字符串,不是左右括号的则直接存储在字符串中

  • JAVA代码参考

  

 1 class Solution {
 2     public String reverseParentheses(String s) {
 3         /*
 4             使用栈来存储左边括号的索引
 5             若当前字符为左边括号(,则将左边括号的索引入栈
 6             若当前字符为右边括号),则将两个索引之间的字符串翻转
 7             最后浏览一遍字符串,不是左右括号的就直接存储在字符串中
 8         */
 9         Stack<Integer> st=new Stack<Integer>();
10         StringBuilder sb=new StringBuilder();
11         char[] arr=s.toCharArray();
12         for(int i=0;i<s.length();++i)
13         {
14             //如果遇到左边括号,则直接将左边括号的索引入栈
15             if(arr[i]=='(')
16             {
17                 st.push(i);
18             }
19             //如果遇到右边括号,则将arr的st.pop()+1到i-1之间的字符
20             else if(arr[i]==')')
21             {
22                 reverse(arr,st.pop()+1,i-1);
23             }
24         }
25         for(int i=0;i<arr.length;++i)
26         {
27             if(arr[i]!='('&&arr[i]!=')')
28             {
29                 sb.append(arr[i]);
30             }
31         }
32         return sb.toString();
33     }
34 
35     void reverse(char[] arr,int left,int right)
36     {
37         while(left<right)
38         {
39             char temp=arr[left];
40             arr[left]=arr[right];
41             arr[right]=temp;
42 
43             ++left;
44             --right;
45         }
46     }
47 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

)

posted @ 2020-07-27 16:23  Cucucu  阅读(473)  评论(0编辑  收藏  举报