前言
熟悉Class Stack.
栈
关于栈—笔者的C语言描述
java.util包有Stack集合类.
JDK17的Stack源码非常简单,能相对轻易看懂.
我们能用Stack类来充当栈,Java框架中LinkedList
(双向链表)实现了双端队列(Deque)
,也能当作栈使用.
Stack类是基于数组实现.
public Stack<E> extends Vector<E>{
...
}
Stack() 构造一个空的栈
E push(E e) 将e入栈,并返回e
E pop() 将栈顶元素出栈并返回
E peek() 获取栈顶元素
int size() 获取栈中有效元素个数
boolean empty() 检测栈是否为空
Stack类里面提供了6种常用方法,JDK17中Stack类里面只有这6种方法.
Stack可以调用父类Vector的方法,若只是当作栈使用,这6种方法足够了.
入栈
:对应push
出栈
:对应pop
,同时可以获取堆顶元素
.
获取堆顶元素
:对应peek
栈使用实例
用栈倒序打印链表元素.
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
var stack = new Stack<Integer>();
for(int x:list) {
stack.push(x);
}
while(!stack.isEmpty())
{
System.out.println(stack.pop()+" ");
}
}
有效的括号
多看几组数据
s = "()"
—true
s = "()[]{}"
—true
s = "(]"
—false
s = ([)]
—false
为什么想到用栈这一数据结构呢?
栈的特点是后进先出.左括号与右括号的匹配是什么样的模式?
显而易见,右括号会与它最近的左括号匹配—若匹配失败则一定不是.
最近一词,显然满足后进先出的特点,与栈不谋而合.
算法设计:
- 遍历字符串,若遇到左括号进行入栈处理.
- 若遇到右括号则进行匹配,过程就是将最近的左括号出栈匹配.
- 匹配失败则结束循环,否则继续按照1,2过程遍历.
- 字符串遍历结束.检查栈是否为空.
- 栈不为空,说明左括号和右括号一定不等,返回false.
另外,字符串长度必须为偶数,才有可能满足上述条件.
class Solution {
public boolean isValid(String s) {
if(s.length()%2==1)
return false;
Stack<Character> stack = new Stack<>();
for(int i = 0;i<s.length();i++)
{
char c = s.charAt(i);
if(c == '('||c == '['||c == '{')
stack.push(c);
else
{
if(stack.isEmpty())
return false;
char c2 = stack.peek();
if((c2=='('&&c ==')')
||(c2=='['&&c==']')
||(c2=='{'&&c=='}'))
stack.pop();
else
return false;
}
}
if(!stack.isEmpty())
return false;
return true;
}
}
用栈实现队列
class MyQueue {
public MyQueue() {
}
public void push(int x) {
}
public int pop() {
}
public int peek() {
}
public boolean empty() {
}
}
偷偷用双端队列秒了这道题
class MyQueue {
Deque<Integer> stack;
public MyQueue() {
stack = new LinkedList<Integer>();
}
public void push(int x) {
stack.addLast(x);
}
public int pop() {
if(stack.isEmpty())
return Integer.MAX_VALUE;
return stack.pollFirst();
}
public int peek() {
if(stack.isEmpty())
return Integer.MAX_VALUE;
return stack.peekFirst();
}
public boolean empty() {
return stack.isEmpty();
}
}
将栈改为队列的过程就是将后进先出改为先进先出
若只是用标准队列(题目要求),单个栈是做不到的.
这里采用双栈实现队列----这里用Stack类
.
队列核心操作—入队
和出队.
为简化过程,我们将一个栈定义为输入栈inStack
,另一个栈为输出栈outStack
.
Stack<Integer> inStack;
Stack<Integer> outStack;
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
压栈的操作很简单:inStack
进行入栈操作即可.
public void push(int x) {
inStack.push(x);
}
出栈的操作那么交给outStack
吧.
想象inStack
的每个元素像各种颜色分层的水.只要将这杯水倒到outStack
这个杯里.假设不考虑密度因素.那么原先杯底的元素现在在杯顶了.—只要将这个outStack
这杯水倒掉,让其重新为空.原先进inStack
的顺序和出outStack
的顺序一样了,满足先进先出.
下面我们翻译成贴近伪代码的描述
算法思想:
- 如果
outStack
的元素不为空,就直接出栈,否则执行第二步. - 将
inStack
的元素依次出栈并压入outStack
直到前者为空栈. - 输出
outStack
的元素.
peek方法大同小异,其它方法自行观察代码很容易.如下:
class MyQueue {
Stack<Integer> inStack;
Stack<Integer> outStack;
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if(empty())
return Integer.MAX_VALUE;
if(outStack.isEmpty())
{
while(!inStack.isEmpty())
{
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
public int peek() {
if(empty())
return Integer.MAX_VALUE;
if(outStack.isEmpty())
{
while(!inStack.isEmpty())
{
outStack.push(inStack.pop());
}
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty()&&outStack.isEmpty();
}
}
validate-stack-sequences
本题翻译一下:已知入队序列,验证popped数组是其出队序列的一种.
你可能有点困惑:
比如,已知1个栈的入栈序列为ABCDE,那么其可能的出队序列.
入栈序列不一定是其这个过程一直入栈而不出栈
假设其一直入栈,然后再依次出栈,那么出栈序列是EDCBA
.
假设其入一个元素,出一个元素,那么出栈序列是ABCDE
.入栈序列和出栈序列一样
那么有什么规律吗?
上面两种情况是不是极其特殊的情况?那么一般情况就是取自两者.
连续入栈的序列,对应出栈序列部分必然是对称的.
入一个出一个,对应出栈序列部分必然相同的.
算法思想:
模拟入栈出栈这一过程.
[1,2,3,4,5]
---->[4,5,3,2,1]
找到入队序列与出队序列的第一个数匹配的位置.
此时栈上:[1,2,3,4]
—匹配到了’4’,将4移除出去.
此时栈上:[1,2,3,5]
—5入栈了,与5匹配.
接下来的3,2,1序列,正好对应出栈3,2,1.
//c语言传统---写数据结构了.
typedef struct{
int *a;
int top;
int size;
}Stack;
Stack *newStack()
{
Stack *stack = (Stack*)malloc(sizeof(Stack));
stack->a = (Stack*)malloc(sizeof(int) * 10);
stack->top=0;
stack->size = 10;
return stack;
}
void push(Stack *stack,int val)
{
if(stack->top==stack->size)
{
stack->a=(int*)realloc(stack->a,sizeof(int)*2*stack->size);
stack->size = 2 * stack->size;
}
stack->a[stack->top++]=val;
}
int pop(Stack *stack)
{
return stack->a[--stack->top];
}
int peek(Stack *stack)
{
return stack->a[stack->top-1];
}
bool isEmpty(Stack *stack)
{
return stack->top==0;
}
bool validateStackSequences(int* pushed, int pushedSize, int* popped, int poppedSize) {
Stack *stack = newStack();
int j = 0;
for(int i = 0;i<pushedSize;i++)
{
push(stack,pushed[i]);
while(!isEmpty(stack)&&j<poppedSize&&peek(stack)==popped[j])
{
pop(stack);
j++;
}
}
return isEmpty(stack);
}
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int j = 0;
for(int i = 0;i<pushed.length;i++)
{
stack.push(pushed[i]);
while(!stack.isEmpty()&&j<popped.length&&stack.peek()==popped[j])
{
stack.pop();
j++;
}
}
return stack.isEmpty();
}
}
Min Stack
最小栈问题.
[-1,0,2,0,-3,-2]
:这样的一个入栈序列.
构建一个辅助栈,来存储最小值.
private Stack<Integer>stack;
private Stack<Integer>minStack;
//构造器
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
首先,如上分出一个普通栈与最小栈.
逻辑:普通栈与最小栈要么都为空栈,要么都非空.----这个逻辑很简单.
只要普通栈存在数,那么必定有最小值,最小栈就不能为空栈.
现在模拟这个序列入栈:
当-1入普通栈时,最小栈为空,-1是最小值.
当0入普通栈,最小栈非空,由于-1<0.0不是这个序列最小值,不如最小栈.
当2入栈同理.
当第二个0入栈时,由于0=0,此时0也要入最小栈.
当-3入普通栈时,-3小于最小栈堆顶0,也要入最小栈.
…
普通栈:[-1,0,2,0,-3,-2]
最小栈:[-1,0,0,-3]
可以发现最小栈就是存储的普通栈子序列的极小值,只要对于全序列,最小栈的堆顶元素才是真正意义的最小值.
模拟入栈:
1. 普通栈一定入栈;最小栈入栈有两种情况:1.空栈必须入栈,2.入栈元素<=当前堆顶元素.
public void push(int val) {
stack.push(val);
if(minStack.isEmpty())
minStack.push(val);
else if(val<=minStack.peek())
minStack.push(val);
else
return ;
}
模拟出栈
1. 若栈为空,返回一个无效值:比如Integer.MAX_VALUE
2. 普通栈一定出栈.
3.最小栈出栈判定:若普通栈出栈元素等于最小栈堆顶元素,说明当前序列最小值出去了,那么最小栈也要出栈.
public void pop() {
if(stack.isEmpty())
return ;
int popVal = stack.pop();
if(popVal==minStack.peek())
{
minStack.pop();
}
}
获取堆顶元素:正常获取直接返回普通栈的堆顶元素
public int top() {
if(stack.empty()){
return Integer.MAX_VALUE;
}
return stack.peek();
}
获取最小值:最小栈堆顶元素即是当前栈序列的最小值,返回最小栈堆顶元素即可.
public int getMin() {
if(minStack.empty())
return Integer.MAX_VALUE;
return minStack.peek();
}
Java解法
class MinStack {
private Stack<Integer>stack;
private Stack<Integer>minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int val) {
stack.push(val);
if(minStack.isEmpty())
minStack.push(val);
else if(val<=minStack.peek())
minStack.push(val);
else
return ;
}
public void pop() {
if(stack.isEmpty())
return ;
int popVal = stack.pop();
if(popVal==minStack.peek())
{
minStack.pop();
}
}
public int top() {
if(stack.empty()){
return Integer.MAX_VALUE;
}
return stack.peek();
}
public int getMin() {
if(minStack.empty())
return Integer.MAX_VALUE;
return minStack.peek();
}
}
这么一来,做了这些题,你大致就能玩熟Stack
了吧.
Deque
实现类LinkedList
也能充当栈使用.
好久没更了,水一篇(^ - ^).