[Java]栈--Stack

前言

熟悉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. 若遇到右括号则进行匹配,过程就是将最近的左括号出栈匹配.
  3. 匹配失败则结束循环,否则继续按照1,2过程遍历.
  4. 字符串遍历结束.检查栈是否为空.
  5. 栈不为空,说明左括号和右括号一定不等,返回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的顺序一样了,满足先进先出.
下面我们翻译成贴近伪代码的描述
算法思想:

  1. 如果outStack的元素不为空,就直接出栈,否则执行第二步.
  2. inStack的元素依次出栈并压入outStack直到前者为空栈.
  3. 输出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也能充当栈使用.
好久没更了,水一篇(^ - ^).

  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值