1. 概述
计算机科学中,stack是一种线性的数据结构,只能在其一端添加数据和移除数据。习惯来说,这一端称之为栈顶,另一端不能操作数据的称之为栈底,就如同生活中的一摞书。
先提供一个栈接口
package com.itheima.datastructure.Stack;
public interface Stack<E> {
/**
* 向栈顶压入元素
*
* @param value 待压入值
* @return 压入成功返回 true, 否则返回 false
*/
boolean push(E value);
/**
* 从栈顶弹出元素
*
* @return 栈非空返回栈顶元素, 栈为空返回 null
*/
E pop();
/**
* 返回栈顶元素, 不弹出
*
* @return 栈非空返回栈顶元素, 栈为空返回 null
*/
E peek();
/**
* 判断栈是否为空
*
* @return 空返回 true, 否则返回 false
*/
boolean isEmpty();
/**
* 判断栈是否已满
*
* @return 满返回 true, 否则返回 false
*/
boolean isFull();
}
2. 链表实现
package com.itheima.datastructure.Stack;
import java.util.Iterator;
public class LinkedListStack<E> implements Stack<E>, Iterable<E> {
static class Node<E> {
E value;
Node<E> next;
public Node(E value, Node<E> next) {
this.value = value;
this.next = next;
}
}
private int capacity = Integer.MAX_VALUE;
private int size = 0;
private Node<E> head = new Node<E>(null, null);
private Node<E> tail = head;
public LinkedListStack(int capacity) {
this.capacity = capacity;
}
@Override
public boolean push(E value) {
if(isFull()) {
return false;
}
// 头插法
head.next = new Node<>(value, head.next);
size++;
return true;
}
@Override
public E pop() {
if(isEmpty()) {
return null;
}
Node<E> first = head.next;
head.next = first.next;
size--;
return first.value;
}
@Override
public E peek() {
if(isEmpty()) {
return null;
}
return head.next.value;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean isFull() {
return size == capacity;
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
Node<E> p = head.next;
@Override
public boolean hasNext() {
return p != null;
}
@Override
public E next() {
E value = p.value;
p = p.next;
return value;
}
};
}
}
3. 数组实现
数组右侧作为栈顶
package com.itheima.datastructure.Stack;
import java.util.Iterator;
public class ArrayStack<E> implements Stack<E>, Iterable<E>{
private final E[] array;
private int top = 0; // 栈顶指针
@SuppressWarnings("all")
public ArrayStack(int capacity) {
this.array = (E[]) new Object[capacity];
}
@Override
public boolean push(E value) {
if(isFull()) {
return false;
}
array[top++] = value;
return true;
}
@Override
public E pop() {
if(isEmpty()) {
return null;
}
E value = array[--top];
return value;
}
@Override
public E peek() {
if(isEmpty()) {
return null;
}
E value = array[top - 1];
return value;
}
@Override
public boolean isEmpty() {
return top == 0;
}
@Override
public boolean isFull() {
return top == array.length;
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
int p = top;
@Override
public boolean hasNext() {
return p > 0;
}
@Override
public E next() {
return array[--p];
}
};
}
}
4. 习题
4.1 有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()" 输出:true
示例 2:
输入:s = "()[]{}" 输出:true
示例 3:
输入:s = "(]" 输出:false
提示:
1 <= s.length <= 10^4
s
仅由括号'()[]{}'
组成
解法一:
①遇到左括号,把要配对的右括号放入栈顶
②遇到右括号,若此时栈为空,返回false,否则把它与栈顶元素对比
- 若相等,栈顶元素弹出,继续对比下一组
- 若不等,无效括号直接返回false
③循环结束
- 若栈为空,表示所有括号都配上对,返回true
- 若栈不为空,表示有没配对的括号,应返回false
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>(); // 创建栈用于存储左括号
Map<Character, Character> brackets = new HashMap<>(); // 创建映射用于存储括号配对
// 初始化括号配对
brackets.put(')', '(');
brackets.put('}', '{');
brackets.put(']', '[');
// 遍历字符串中的每个字符
for (char c : s.toCharArray()) {
// 如果是右括号
if (brackets.containsKey(c)) {
// 栈为空或者栈顶元素与当前右括号不匹配,返回false
if (stack.isEmpty() || stack.peek() != brackets.get(c)) {
return false;
}
stack.pop(); // 匹配成功,弹出栈顶元素
} else {
stack.push(c); // 左括号入栈
}
}
return stack.isEmpty(); // 返回栈是否为空
}
}
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 c = s.charAt(i);
// 左括号
if(c == '(') {
stack.push(')');
} else if(c == '[') {
stack.push(']');
} else if(c == '{') {
stack.push('}');
} else {
// 右括号
if(!stack.isEmpty() && stack.peek() == c) {
stack.pop();
} else {
return false;
}
}
}
return stack.isEmpty();
}
}
4.2 逆波兰表达式求值
给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
示例 1:
输入:tokens = ["2","1","+","3","*"] 输出:9 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"] 输出:6 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] 输出:22 解释:该算式转化为常见的中缀算术表达式为: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22
提示:
1 <= tokens.length <= 10^4
tokens[i]
是一个算符("+"
、"-"
、"*"
或"/"
),或是在范围[-200, 200]
内的一个整数
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如
( 1 + 2 ) * ( 3 + 4 )
。 - 该算式的逆波兰表达式写法为
( ( 1 2 + ) ( 3 4 + ) * )
。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成
1 2 + 3 4 + *
也可以依据次序计算出正确结果。 - 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
解法一:
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> st = new Stack<>();
for(String token : tokens) {
if(token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")) {
int num2 = st.pop();
int num1 = st.pop();
switch(token) {
case "+":
st.push(num1 + num2);
break;
case "-":
st.push(num1 - num2);
break;
case "*":
st.push(num1 * num2);
break;
case "/":
st.push(num1 / num2);
break;
}
} else {
st.push(Integer.parseInt(token));
}
}
return st.pop();
}
}
或:需要JDK17+
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> st = new Stack<>();
for (String token : tokens) {
switch (token) {
case "+" -> {
Integer num2 = st.pop();
Integer num1 = st.pop();
st.push(num1 + num2);
}
case "-" -> {
Integer num2 = st.pop();
Integer num1 = st.pop();
st.push(num1 - num2);
}
case "*" -> {
Integer num2 = st.pop();
Integer num1 = st.pop();
st.push(num1 * num2);
}
case "/" -> {
Integer num2 = st.pop();
Integer num1 = st.pop();
st.push(num1 / num2);
}
default -> st.push(Integer.parseInt(token));
}
}
return st.pop();
}
}
解法三:
public int evalRPN(String[] tokens) {
LinkedList<Integer> numbers = new LinkedList<>();
for (String t : tokens) {
switch (t) {
case "+" -> {
Integer b = numbers.pop();
Integer a = numbers.pop();
numbers.push(a + b);
}
case "-" -> {
Integer b = numbers.pop();
Integer a = numbers.pop();
numbers.push(a - b);
}
case "*" -> {
Integer b = numbers.pop();
Integer a = numbers.pop();
numbers.push(a * b);
}
case "/" -> {
Integer b = numbers.pop();
Integer a = numbers.pop();
numbers.push(a / b);
}
default -> numbers.push(Integer.parseInt(t));
}
}
return numbers.pop();
}
4.3 中缀表达式转后缀
package com.itheima.datastructure.Stack;
import java.util.LinkedList;
public class E03InfixToSuffix {
/**
* 思路
* 1. 遇到数字,拼串
* 2. 遇到 + - * /
* - 优先级高于栈顶运算符 入栈
* - 否则将栈中高级或平级运算符出栈拼串,本运算符入栈
* 3. 遍历完成,栈中剩余运算符出栈拼串
* -先出栈,意味着优先运算
* 4. 带 ()
* - 左括号直接入栈
* - 右括号要将栈中直至左括号为止的运算符出栈拼串
*/
public static void main(String[] args) {
System.out.println(infixToSuffix("a+b"));
System.out.println(infixToSuffix("a+b-c"));
System.out.println(infixToSuffix("a+b*c"));
System.out.println(infixToSuffix("a*b-c"));
System.out.println(infixToSuffix("(a+b)*c"));
System.out.println(infixToSuffix("a+b*c+(d*e+f)*g"));
}
static String infixToSuffix(String exp) {
LinkedList<Character> stack = new LinkedList<>();
StringBuilder sb = new StringBuilder(exp.length());
for (int i = 0; i < exp.length(); i++) {
char c = exp.charAt(i);
switch (c) {
case '+', '-', '*', '/' -> {
if(stack.isEmpty()) {
stack.push(c);
} else {
if(priority(c) > priority(stack.peek())) { // 当前运算符比栈顶运算符优先级高
stack.push(c);
} else {
while(!stack.isEmpty() && priority(stack.peek()) >= priority(c)) { //将栈中高级或平级运算符出栈拼串
sb.append(stack.pop());
}
stack.push(c); // 本运算符入栈
}
}
}
case '(' -> {
stack.push(c);
}
case ')' -> {
while (!stack.isEmpty() && stack.peek() != '(') {
sb.append(stack.pop());
}
stack.pop(); // '('出栈
}
default -> {
sb.append(c);
}
}
}
while(!stack.isEmpty()) {
sb.append(stack.pop());
}
return sb.toString();
}
private static int priority(char c) {
return switch (c) {
case '(' -> 0;
case '*', '/' -> 2;
case '+', '-' -> 1;
default -> throw new IllegalArgumentException("不合法字符:" + c);
};
}
}
4.4 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入: ["MyQueue", "push", "push", "peek", "pop", "empty"] [[], [1], [2], [], [], []] 输出: [null, null, null, 1, 1, false] 解释: MyQueue myQueue = new MyQueue(); myQueue.push(1); // queue is: [1] myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) myQueue.peek(); // return 1 myQueue.pop(); // return 1, queue is [2] myQueue.empty(); // return false
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
进阶:
- 你能否实现每个操作均摊时间复杂度为
O(1)
的队列?换句话说,执行n
个操作的总时间复杂度为O(n)
,即使其中一个操作可能花费较长时间。
解法一:用stack2模拟队列头,stack1模拟队列尾
class MyQueue {
private Stack<Integer> stack1; // 主栈
private Stack<Integer> stack2; // 辅助栈
public MyQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
// 先把stack1的所有元素移动到stack2,stack2再出栈
public int pop() {
if (stack2.isEmpty()) { // 如果不为空,直接弹stack2的栈顶即可
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
4.5 用队列模拟栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
注意:
- 你只能使用队列的标准操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入: ["MyStack", "push", "push", "top", "pop", "empty"] [[], [1], [2], [], [], []] 输出: [null, null, null, 2, 2, false] 解释: MyStack myStack = new MyStack(); myStack.push(1); myStack.push(2); myStack.top(); // 返回 2 myStack.pop(); // 返回 2 myStack.empty(); // 返回 False
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、top
和empty
- 每次调用
pop
和top
都保证栈不为空
进阶:你能否仅用一个队列来实现栈。
解法一:两个队列实现栈
思路:
- 入栈:直接添加到q1的末尾;
- 出栈:先将前n- 1个元素从q1移到q2,q1中剩下的一个元素即为栈顶,然后再将q2中的元素恢复到q1
- 栈顶:每次最新入栈的元素
- 判空:q1是否为空
import java.util.LinkedList;
import java.util.Queue;
class MyStack {
private Queue<Integer> q1; // 主队列
private Queue<Integer> q2; // 辅助队列
private int topElement;
/** Initialize your data structure here. */
public MyStack() {
q1 = new LinkedList<>();
q2 = new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
q1.offer(x);
topElement = x;
}
/** Removes the element on the top of the stack and returns that element. */
public int pop() {
while (q1.size() > 1) {
topElement = q1.poll(); // Get and remove the front element
q2.offer(topElement); // Add it to the second queue
}
int result = q1.poll(); // The last remaining element is the top of the stack
// Swap the queues
Queue<Integer> temp = q1;
q1 = q2;
q2 = temp;
return result;
}
/** Get the top element. */
public int top() {
return topElement;
}
/** Returns whether the stack is empty. */
public boolean empty() {
return q1.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
解法二:一个队列实现栈
思路:
- 出栈:直接移除队头元素
- 入栈:将元素入队;将前size - 1个元素先从队列中移除的同时重新添加到队尾
class MyStack {
private Queue<Integer> q;
public MyStack() {
q = new LinkedList<>();
}
// 通过多次反转队列中的元素保证最新添加的元素位于队列的前面
public void push(int x) {
// 将x加入队列
q.offer(x);
// 重新调整队列,使得x在队头
int size = q.size();
for(int i = 0; i < size - 1; i++) {
q.offer(q.poll());
}
}
public int pop() {
return q.poll();
}
public int top() {
return q.peek();
}
public boolean empty() {
return q.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/