主要解决最近相关性问题
题目
- https://leetcode-cn.com/problems/valid-parentheses/
- https://leetcode-cn.com/problems/min-stack/
- https://leetcode-cn.com/problems/largest-rectangle-in-histogram
- https://leetcode-cn.com/problems/sliding-window-maximum
- https://leetcode.com/problems/design-circular-deque
- https://leetcode.com/problems/trapping-rain-water/
- 分析 Queue 和 Priority Queue 的源码
- 用 add first 或 add last 这套新的 API 改写 Deque 的代码
如何理解栈
后进者先出,先进者后出,这就是典型的“栈”结构。
栈是一种“操作受限”的线性表,只允许在一端插入和删除数据
从功能上来说,数组或链表确实可以替代栈,但你要知道,特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。
当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。
如何实现栈
主要:
------ 入栈 —>栈顶插入一个数据
------ 出栈 ---->栈顶删除一个数据
数组实现----->顺序栈
链表实现----->链式栈
// 基于数组实现的顺序栈
public class ArrayStack {
private String[] items; // 数组
private int count; // 栈中元素个数
private int n; //栈的大小
// 初始化数组,申请一个大小为n的数组空间
public ArrayStack(int n) {
this.items = new String[n];
this.n = n;
this.count = 0;
}
// 入栈操作
public boolean push(String item) {
// 数组空间不够了,直接返回false,入栈失败。
if (count == n) return false;
// 将item放到下标为count的位置,并且count加一
items[count] = item;
++count;
return true;
}
// 出栈操作
public String pop() {
// 栈为空,则直接返回null
if (count == 0) return null;
// 返回下标为count-1的数组元素,并且栈中元素个数count减一
String tmp = items[count-1];
--count;
return tmp;
}
}
支持动态扩容的顺序栈
像支持动态扩容的容器一样,当栈满了以后,申请一个更大的数组,将原来的数据拷贝到新的数组中
那么支持动态扩容的栈入栈和出栈的时间复杂度是多少呢
入栈 ----(直接插入)最好时间复杂度O(1) -----(栈满了,扩容,拷贝)最差时间复杂度O(n)
-----均摊时间复杂度O(1)
栈在函数调用中的应用
操作系统中给线程分配内存空间,叫做栈,用来储存函数调用时的临时变量,函数调用的过程中不断压栈(调用)和出栈(return),参考递归函数调用 。
栈在表达式求值中的应用
编译器如何利用栈来实现表达式求值
编译器通过两个栈来实现,一个报错操作数的栈,另一个保存运算符的栈。
例子:3+5*8-6
遇到数字 ---->直接压入操作数栈
遇到操作符----->就与操作符栈的栈顶操作符比较---->如果比栈顶元素优先级高---->就压栈
----->如果比栈顶元素优先级低或相同--
----->从运算符栈中取栈顶运算符,从操作数的栈顶取两个操作数
----->然后计算,再把计算完的结果压入操作数栈
栈在括号匹配中的应用
假设表达式中只包含三种括号,圆括号 ()、方括号[]和花括号{},并且它们可以任意嵌套。比如,{[] ()[{}]}或[{()}([])]等都为合法格式,而{[}()]或[({)]为不合法的格式。现在给你一个包含三种括号的表达式字符串,如何检查它是否合法呢?
我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。
栈实现浏览器前进后退
我们使用两个栈,X 和 Y,我们把首次浏览的页面依次压入栈 X,当点击后退按钮时,再依次从栈 X 中出栈,并将出栈的数据依次放入栈 Y。当我们点击前进按钮时,我们依次从栈 Y 中取出数据,放入栈 X 中。当栈 X 中没有数据时,那就说明没有页面可以继续后退浏览了。当栈 Y 中没有数据,那就说明没有页面可以点击前进按钮浏览了
C++实现栈
typedef struct
{
StackDataType * elem;
int top;
int size;
int increment;
}SqStack;
//初始化栈
Status initStack(SqStack &s, int size, int inc );
//销毁栈
Status destoryStack(SqStack &s);
//判断栈是否为空
Status emptyStack(SqStack &s);
//清空栈
Status clearStack(SqStack &s);
//将元素e压栈
Status pushStack(SqStack &s, StackDataType e);
//取出栈顶元素,用e返回
Status getTopStack(SqStack &s, StackDataType &e);
//出栈
Status popStack(SqStack &s, StackDataType &e);
//初始化栈
Status initStack(SqStack &S, int size, int inc )
{
S.elem = (StackDataType *)malloc(size * sizeof(StackDataType));
if(NULL == S.elem)
return OVERFLOW;
S.top = 0;
S.size = size;
S.increment = inc;
return OK;
}
//销毁栈
Status destoryStack(SqStack &S)
{
free(S.elem);
S.elem = NULL;
return OK;
}
//判断栈是否为空
Status emptyStack(SqStack &S)
{
if(0 == S.top)
return TRUE;
return FALSE;
}
//清空栈
Status clearStack(SqStack &S)
{
if(0 != S.top)
{
S.size = 0;
S.top = 0;
}
return OK;
}
//将元素e压栈
Status pushStack(SqStack &S, StackDataType e)
{
StackDataType * newBase;
if(S.top >= S.size)
{
newBase = (StackDataType *)realloc(S.elem, (S.size + S.increment));
if(NULL == newBase) return OVERFLOW;
S.elem = newBase;
S.size += S.increment;
}
S.elem[S.top++] = e;
return OK;
}
//取出栈顶元素,用e返回
Status getTopStack(SqStack &S, StackDataType &e)
{
if(0 == S.top) return OVERFLOW;
e = S.elem[S.top - 1];
return OK;
}
//出栈
Status popStack(SqStack &S, StackDataType &e)
{
if(0 == S.top) return OVERFLOW;
e = S.elem[S.top - 1];
S.top--;
return OK;
}