目录

一、栈(Stack)的介绍

🛫概念

二、栈的使用

🛫Stack 的常用方法

🛫栈的模拟实现

🛫栈的练习

💦不可能的出栈顺序

💦LeetCode -- 逆波兰表达式求值

💦牛客网 -- 栈的压入、弹出序列

💦LeetCode -- 有效的括号

💦LeetCode -- 最小栈


一、栈(Stack)的介绍

🛫概念

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。

🍉说明:

  • 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
  • 栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
  • 出栈:栈的删除操作叫做出栈。出数据在栈顶。

Java虚拟机栈

  • JVM stack(Java虚拟机栈)只是JVM中的一块内存,该内存一般用于存放。例如:局部变量......
  • 这块内存同样具备栈的特性。例如在调用函数的时候,会为函数开辟一块内存,开辟的内存叫做栈帧。

二、栈的使用

在集合框架中,Stack(栈)是一个普通的类,实现了List接口,具体框架图如下:

🍉说明:

  1. Stack实现了RandomAccess接口,表明Stack支持随机访问
  2. Stack实现了Cloneable接口,表明Stack是可以clone的
  3. Stack实现了Serializable接口,表明Stack是支持序列化的
  4. Vector(向量)是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。
  5. Vector 与 ArrayList 基本是一致的,不同的是Vector是线程安全的,会在可能出现线程安全的方法前面使用 synchronized 关键字。

🛫Stack 的常用方法

🛫栈的模拟实现

  • Java底层的栈使用了泛型,可以操作任何类型的数据,这里模拟实现使用了顺序表(整型数组)。

🌊代码示例:

import java.util.Arrays;

public class MyStack {
    public int[] elem; //数组 -> 栈空间
    public int usedSize;//有效数据
    public MyStack(){
        this.elem = new int[5];
        this.usedSize = 0;
    }
    //入栈
    public void push(int val){
        //如果栈满了就进行扩容
        if(isFull()){
            this.elem = Arrays.copyOf(elem,2*this.elem.length);
        }
        this.elem[this.usedSize] = val;
        usedSize++;
    }
    //判断栈是否满
    public boolean isFull(){
        return usedSize==this.elem.length;
    }

    //出栈
    public int pop(){
        if(isEmpty()){
            throw new NullPointerException("栈为空!");
        }
        int oldVal = this.elem[usedSize-1];
        this.usedSize--;
        return oldVal;
    }
    //判断栈是否为空
    public boolean isEmpty(){
        return this.usedSize==0;
    }

    //读取栈顶元素
    public int peek(){
        if(isEmpty()){
            throw new NullPointerException("栈为空!");
        }
        return this.elem[usedSize-1];
    }
}

🌊测试代码:

import java.util.Stack;

public class TestDemo1 {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        myStack.push(1);//入栈
        myStack.push(2);
        myStack.push(3);
        myStack.push(4);
        System.out.println(myStack.peek());//获取栈顶元素
        System.out.println(myStack.pop());//出栈
        System.out.println(myStack.pop());
        System.out.println(myStack.pop());
        System.out.println(myStack.pop());
        System.out.println(myStack.isEmpty());//判断栈是否为空,如果为空返回true,否则返回false
    }
}

运行结果:
4
4
3
2
1
true

Java底层的栈是使用数组实现的,使用链表也是可以实现。

使用链表需要满足的条件:

  1. 先进后出
  2. 入栈和出栈的时间复杂度为O(1)

链表可以头插也可以尾插,那么入栈是使用头插法还是使用尾插法呢?

  • 假设:如果入栈使用尾插法,那么时间复杂度是O(n),因为尾插法每次都要找最后一个结点。
  • 假设:如果入栈使用头插法,那么时间复杂度是O(1);出栈的时候只需要删除头结点,时间复杂度也是O(1)

经过推导得出,使用头插法实现栈使满足条件,但是如果非要使用尾插法呢?

  • 假设给一个last引用指向尾巴结点,此时入栈的时间复杂度为O(1)。
  • 但是出栈的时间复杂度还是O(n)。因为,虽然知道了最后一个结点,但是去掉尾巴结点后还要知道它的前一个结点,那么就要遍历单链表去找尾巴结点的前驱结点。此时时间复杂度又是O(n)。

最终的解决办法就是使用双向链表来实现一个栈。(双向链表可以知道一个结点的前驱结点与后继结点)。

🛫栈的练习

💦不可能的出栈顺序

一个栈的入栈序列是a,b,c,d,e则出栈的不可能的输出序列是:C

  • A:edcba
  • B:decba
  • C:dceab
  • D:abcde

解题思路:

💦LeetCode -- 逆波兰表达式求值

📌题目描述:

  • 根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
  • 注意:两个整数之间的除法只保留整数部分。
  • 可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

🍓逆波兰表达式:逆波兰表达式是一种后缀表达式,所谓后缀就是指运算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

前、中、后缀表达式的转换:

  • 将中缀表达式按运算顺序加上括号,分别将运算符移到对应括号的最右边,再去掉所有括号,就能得到后缀表达式。
  • 同理将运算符移到对应括号的最左边就得到了前缀表达式。

🌌逆波兰表达式主要有以下两个优点:

  1. 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  2. 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

📋题目示例:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

解题思路:

  • 创建一个存放整型数据的栈。遍历字符串数组,遇到数字就入栈。遇到运算符则取出栈顶的两个元素进行计算,再将计算后的结果入栈。
  • 写一个方法isOperation(),判断字符串数组中的字符是不是运算符。
  • 遍历字符串数组,调用isOperation()方法。如果当前字符不是运算符,则将字符转换为对应的十进制整数并入栈。如果当前字符是运算符则取出两个元素进行计算(出栈)。再计算后的结果入栈。
  • 需要注意:先出栈的元素放到运算符的右边,后出栈的元素放到运算符的左边。
  • 最终栈顶元素的值为计算的结果,返回栈顶元素即可。

🌊代码示例:

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for(int i=0;i<tokens.length;i++){
            String str = tokens[i];
            if(isOperation(str)==false){
                stack.push(Integer.parseInt(str));
            }else{
                int num2 = stack.pop();
                int num1 = stack.pop();
                switch(str){
                    case"+": stack.push(num1+num2);break;
                    case"-": stack.push(num1-num2);break;
                    case"*": stack.push(num1*num2);break;
                    case"/": stack.push(num1/num2);break;
                }
            }
        }
        return stack.pop();
    }

    private boolean isOperation(String str){
        if(str.equals("+")||str.equals("-")||str.equals("*")||str.equals("/")){
            return true;
        }
        return false;
    }
}

💦牛客网 -- 栈的压入、弹出序列

📌题目描述:

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

  • 0<=pushV.length == popV.length <=1000
  •  -1000<=pushV[i]<=1000
  • pushV 的所有数字均不相同

📋题目示例:

输入:[1,2,3,4,5],[4,5,3,2,1]

返回值:true

说明:
可以通过 push(1)=>push(2)=>push(3)=>push(4)=>pop()=>push(5)=>pop()=>pop()=>pop()=>pop()
这样的顺序得到[4,5,3,2,1]这个序列,返回true      

解题思路:

  • 创建一个存放整型数据的栈。遍历入栈序列数组,将所有元素入栈。
  • 将栈顶元素与出栈序列数组进行比较,如果相等就出栈,依次类推,直到栈为空或者遍历完出栈序列数组。
  • 最终判断栈是否为空,如果栈为空则入栈序列与出栈序列相同,返回true;否则返回false。

🌊代码示例:

import java.util.ArrayList;
import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for(int i=0;i<pushA.length;i++){
            stack.push(pushA[i]);
            while(j<popA.length && !stack.empty() && stack.peek() == popA[j]){
                stack.pop();
                j++;
            }
        }
        return stack.empty();
    }
}

💦LeetCode -- 有效的括号

📌题目描述:

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。

📋题目示例:

示例1:
输入:s = "()[]{}"
输出:true

示例2:
输入:s = "([)]"
输出:false

解题思路:

  • 创建一个存放字符数据的栈。遍历字符串,使用charAt()方法获取字符串中的每一个字符。
  • 如果字符是左括号就进行入栈操作(左括号:'(' , '[' , '{' )。

1.分析括号不匹配的情况有三种:

  1. 左括号多:(((((   )))
  2. 右括号多:(((    )))))
  3. 左右括号次序不匹配: [   ]

2.不匹配:

  • 因为栈当中存储的是左括号,如果当 i 遍历完整个字符串,栈还是不为空,那么就是左括号多。返回false。
  • 如果当前字符是右括号,且栈为空,那么就是右括号多,返回false。
  • 如果当前字符是右括号,栈顶元素与当前的右括号字符不匹配,返回false。

3.匹配: [  ]  )

  • 如果字符是右括号,判断栈顶元素与当前的右括号字符是否匹配,如果匹配就进行出栈操作,直到所有元素都出栈,栈为空返回true。

🌊代码示例:

import java.util.Stack;

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();

        for(int i=0;i<s.length();i++){
            char ch = s.charAt(i);
            if(ch=='('||ch=='{'||ch=='['){
                //将左括号入栈
                stack.push(ch);
            }else{
                //右括号多了
                if(stack.empty()){
                    return false;
                }
                char top = stack.peek();
                if(top=='('&&ch==')'||top=='{'&&ch=='}'||top=='['&&ch==']'){
                    stack.pop();
                }else{
                    return false;
                }
            }
        }
        //如果栈不为空,说明左括号多了
        return stack.empty();
    }
}

💦LeetCode -- 最小栈

📌题目描述:

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

📋题目示例:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

解题思路:

创建两个栈,一个普通栈,一个最小栈。

入栈:

  • 所有元素都要放入普通栈,判断元素是否放入最小栈,如果最小值为空,则直接将元素入最小栈。
  • 如果最小栈中有元素,将待入栈元素与最小栈的栈顶元素进行比较,如果待入栈元素小于或等于最小栈中的栈顶元素,则将元素也放入最小栈,否则就不放入最小栈。

出栈:

  • 普通栈中的元素都要进行出栈操作。如果最小栈的栈顶元素等于普通栈的栈顶元素,那么最小栈也进行出栈操作,否则最小栈中的元素不出栈。

返回值:

  • top()方法返回普通栈中的栈顶元素
  • getMin()方法返回最小栈的栈顶元素

🌊代码示例:

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.empty()){
            minStack.push(val);
        }else{
            //比较小于等于,最小栈才入栈
            if(val<=minStack.peek()){
                minStack.push(val);
            }
        }
    }
    
    public void pop() {
        int stacktop = stack.pop();
        if(stacktop == minStack.peek()){
            minStack.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐