一.什么是栈?
- 栈是一种特殊的线性表, 其只允许在固定的一端进行插入和删除操作; 进行数据插入和删除操作的一端称为栈顶, 另一端称为栈底
- 栈中的数据元素遵守 "先进后出(LIFO)"的原则
- 压栈: 栈的插入操作叫做进栈/压栈/入栈, 数据进入到栈顶
- 出栈: 栈的删除操作叫做出栈, 出栈的数据位于栈顶

二.栈的使用
- 常用方法说明
方法 | 功能 |
Stack() | 构造一个空栈 |
push(E e): E | 将 e 入栈 |
pop(): E | 弹出栈顶元素并返回值 |
peek(): E | 获取栈顶元素 |
empty(): boolean | 判断栈是否为空 |
size(): int | 获取栈中的元素个数 |
clear() | 清空栈 |
- 代码体现
/**
* @Author 林沐雨
* @Date 2024/12/12
*/
public class StackUse {
public static void main(String[] args) {
// 申请栈
Stack<Integer> stack = new Stack<>();
// 入栈
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
// 打印栈中的元素
System.out.println(stack);
// 出栈
System.out.println(stack.pop());
System.out.println(stack.pop());
// 获取栈中元素的个数
System.out.println(stack.size());
System.out.println(stack);
// 获取栈顶元素
System.out.println(stack.peek());
// 判断栈是否为空
System.out.println(stack.empty());
// 清空栈
stack.clear();
}
}
三.栈的模拟实现
- MyStack类: 实现栈的主要功能
/**
* @Author 林沐雨
* @Date 2024/12/12
*/
public class MyStack {
private int elem[];
// 底层数组的默认大小
private static final int DEFAULT_CAPACITY = 10;
// 数组的有效元素个数
private int size;
// 无参构造器
public MyStack() {
this.elem = new int[DEFAULT_CAPACITY];
}
// 有参构造器
public MyStack(int size) {
this.elem = new int[size];
}
Stack<Integer> stack = new Stack();
// **************** 栈中的方法 ****************** \\
// 入栈
public void push(int val) {
// 判断栈是否满了
if (size == this.elem.length) {
// 扩容
this.elem = Arrays.copyOf(this.elem, this.elem.length * 2);
}
// 入栈
this.elem[size++] = val;
}
// 出栈
public int pop() {
// 判断栈是否为空
if (size == 0) {
throw new StackIsEmptyException("栈为空,无法出栈!");
}
// 出栈
// 有效元素个数 -1
size--;
// 返回要弹出的元素值
return this.elem[size];
}
// 获取栈顶元素
public int peek() {
// 判断栈是否为空
if (size == 0) {
throw new StackIsEmptyException("栈为空,无法出栈!");
}
return this.elem[size - 1];
}
// 栈的元素个数
public int size() {
return size;
}
// 打印栈中的元素
public void display() {
for (int i = 0; i < size; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
// 清空栈
public void clear() {
// 长度为0,元素中有效个数清空
size = 0;
// 把 elem 数组置空
this.elem = null;
}
}
- NullStackException类: 栈空异常类
/**
* @Author 林沐雨
* @Date 2024/12/12
*/
public class StackIsEmptyException extends RuntimeException {
public StackIsEmptyException() {
}
public StackIsEmptyException(String message) {
super(message);
}
}
- MyStackTest类: 测试类, 用于测试模拟的功能是否正常
/**
* @Author 林沐雨
* @Date 2024/12/12
*/
public class Test {
public static void main(String[] args) {
MyStack stack = new MyStack();
// 入栈
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.display();
System.out.println(stack.size());
// 出栈
System.out.println(stack.pop());
System.out.println(stack.pop());
stack.display();
System.out.println(stack.size());
// 获取栈顶元素
System.out.println(stack.peek());
stack.pop();
System.out.println(stack.peek());
// 清空栈
stack.clear();
// 报错,因为已经把数组置空了
// stack.push(1);
}
}
四.栈的常见算法题
1.逆序打印链表
- 题目描述
给你一个链表, 请把链表中的内容进行逆序打印
- 思路分析
法一: 递归; 递归链表, 直到链表一直递归到最后一个元素, 然后从最后一个开始向前打印
法二: 使用栈辅助; 把链表中的节点存储在栈中, 然后出栈并打印节点内容
- 代码实现
/**
* 逆序打印链表
* 法一: 递归
* 时间复杂度: O(N)
* 空间复杂度: O(N)
*/
public void printReverse1(Node head) {
// head 不为空, 进行递归
if (null != head) {
// 进行递归
printReverse(head.next);
// 打印值
System.out.print(head.val + " ");
}
}
/**
* 逆序打印链表
* 法二: 栈辅助
* 时间复杂度: O(N)
* 空间复杂度: O(N)
*/
public void printReverse(Node head) {
// 判空
if (head == null) return;
// 创建栈
Stack<Node> stack = new Stack<>();
// 入栈
Node cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
// 出栈并打印值, 这个打印就是逆序打印
while (!stack.isEmpty()) {
Node pop = stack.pop();
System.out.print(pop.val + " ");
}
}
2.括号匹配
- 题目描述: 20. 有效的括号 - 力扣(LeetCode)
给定一个只包括 '(', ')', '{', '}', '[', ']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
- 思路分析
使用栈空间来实现:全部是左括号的情况;全部是右括号的情况;遇到左括号就入栈;
当遇到右括号后, 判断是否右匹配的左括号, 有则出栈;;没有则说明不匹配
- 代码实现
/**
* 有效的括号
* 时间复杂度: O(N)
* 空间复杂度: O(N)
*/
public boolean isValid(String s) {
// 字符串长度
int len = s.length();
// 创建栈
Stack<Character> stack = new Stack<>();
for (int i = 0; i < len; i++) {
// 获取当前字符
char ch = s.charAt(i);
// 左括号入栈
if (isLeftBracket(ch)) {
stack.push(ch);
} else {
// 右括号
// 空栈则无法匹配, 返回 false
if (stack.isEmpty()) {
return false;
}
// 不是空栈则进行括号匹配
char tmp = stack.peek();
// 判断左右是否匹配
if (isMatches(tmp, ch)) {
stack.pop();
} else {
// 不匹配则返回 false
return false;
}
}
}
// 跳出循环后,还需要判断全部是左括号的情况
// 如歌栈为空,则说明全部匹配
return stack.isEmpty();
}
// 判断是不是左括号
private boolean isLeftBracket(char ch) {
return ch == '[' || ch == '(' || ch == '{';
}
// 判断左右括号是否匹配
private boolean isMatches(char tmp, char ch) {
if ((tmp == '[' && ch == ']') || (tmp == '{' && ch == '}')
|| (tmp == '(' && ch == ')')) {
return true;
}
return false;
}
3.逆波兰表达式求值
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。请你计算该表达式。返回一个表示表达式值的整数。注意:
- 有效的算符为 '+'、'-'、'*' 和 '/' 。
- 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
- 思路分析
栈空间辅助;遇到数字则入栈, 遇到符号则出栈(出栈两次),
出栈后的数字使用对应的符号进行操作, 然后把结果再入栈,最后弹出结果
- 代码实现
/**
* 逆波兰表达式
* 时间复杂度: O(N)
* 空间复杂度: O(N)
*/
public int evalRPN(String[] tokens) {
// 获取数组长度
int len = tokens.length;
// 创建栈
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < len; i++) {
// 获取数组中第 i 个字符串
String s = tokens[i];
// 是数字则入栈
if (isNumber(s)) {
// 把字符串转为数字后入栈
stack.push(Integer.parseInt(s));
} else {
// 不是数组,则说明是运算符
// 弹出元素
// 注意:我们弹出的第一个数作为右操作数,防止除 0 异常
int right = stack.pop();
int left = stack.pop();
// 使用操作符进行操作
switch (s) {
case "+":
stack.push(left + right);
break;
case "-":
stack.push(left - right);
break;
case "*":
stack.push(left * right);
break;
case "/":
stack.push(left / right);
break;
}
}
}
// 返回结果
return stack.pop();
}
// 判断当前字符串是不是数字
private boolean isNumber(String s) {
return !("+".equals(s) || "-".equals(s) || "*".equals(s) || "/".equals(s));
}
4.验证栈序列
给定 pushed 和 popped 两个序列,每个序列中的 值都不重复 ,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true ;否则,返回 false 。
- 思路分析
循环把 pushed 数组的值全部入栈, 在入栈过程中判断 poped 数组的值是否和入栈的元素相同, 相同则出栈, 不相同进行下一次判断, 最后判断栈是否为空, 为空则说明栈序列相同
- 代码详解
/**
* 判断栈序列
* 时间复杂度: O(N)
* 空间复杂度: O(N)
*/
public boolean validateStackSequences(int[] pushed, int[] popped) {
// 创建栈
Stack<Integer> stack = new Stack<>();
// 出栈数组和入栈数组长度不相同的情况
if (pushed.length != popped.length) return false;
// 长度相同
for (int i = 0, count = 0; i < pushed.length; i++) {
// 入栈
stack.push(pushed[i]);
// 1.判断栈是否为空
// 2.出栈数组的值和栈中的值相同,则弹出栈中的值,且判断出栈数组的下一个和下一个栈顶元素
while (!stack.isEmpty() && popped[count] == stack.peek()) {
stack.pop();
count++;
}
}
// 栈空: 说明入栈和出栈相匹配
return stack.isEmpty();
}
5.最小栈
- 题目描述: 155. 最小栈 - 力扣(LeetCode)
设计一个支持 push , pop , top 操作,并能在常数时间内检索到最小元素的栈。实现 MinStack 类:
- MinStack() 初始化堆栈对象。
- void push(int val) 将元素val推入堆栈。
- void pop() 删除堆栈顶部的元素。
- int top() 获取堆栈顶部的元素。
- int getMin() 获取堆栈中的最小元素。
- 思路分析
两个栈辅助实现: 一个栈用于正常的进栈和出栈; 另一个栈只用于入最小值和出最小值的栈
- 代码详解
/**
* 最小栈
*/
class MinStack {
// 正常的出入栈
private Stack<Integer> stack;
// 只出入最小值的栈
private Stack<Integer> minStack;
// 初始化
public MinStack() {
// 对正常栈和最小值栈进行初始化
stack = new Stack<>();
minStack = new Stack<>();
}
/**
* 入栈
* stack 进行正常的入栈
* minStack 为空时, 入栈; 不为空时, 比较栈顶元素是否比要入栈的值大, 大则入栈
*/
public void push(int val) {
// 入栈
stack.push(val);
// 栈空判读
if (minStack.isEmpty()) {
minStack.push(val);
} else if (minStack.peek() >= val) {
// 判断 minStack 的栈顶元素是否比 val 大
minStack.push(val);
}
}
/**
* 出栈
* stack 正常出栈
* minStack 出栈需要判断栈顶元素是否和stack出栈的元素是否相同, 相同则出栈
*/
public void pop() {
// stack 出栈
int val = stack.pop();
// 判断 val 是否和 minStack 栈顶元素值是否相同
if (val == minStack.peek()) {
// 相同出栈
minStack.pop();
}
}
/**
* 获取 stack 栈的栈顶元素
*/
public int top() {
// 栈空
if (stack.isEmpty()) return -1;
// 返回栈顶元素
return stack.peek();
}
/**
* 获取最小栈的栈顶元素
*/
public int getMin() {
// 栈空
if (minStack.isEmpty()) return -1;
// 返回最小栈的栈顶元素
return minStack.peek();
}
}