本篇博客旨在整理记录自已对栈的一些总结,以及刷题的解题思路,同时希望可给小伙伴一些帮助。本人也是算法小白,水平有限,如果文章中有什么错误之处,希望小伙伴们可以在评论区指出来,共勉 💪。
栈的概述
栈(stack): 是一种线性的数据结构,也是一个 先入后出 的有序列表。只能在其一端添加数据和移除数据。习惯来说,这一端称之为栈顶,另一端不能操作数据的称之为栈底,就如同生活中的一摞书。
栈的基本操作
- void push(int data):入栈(将数据data插入到栈中)
- int pop():出栈(删除并返回最后一个插入栈的元素)
- int top():返回最后一个插入栈的元素,但不删除
- int size():返回存储在栈中的元素个数
- boolean isEmpty():返回栈是否是空栈
- boolean isFull():返回是否是满栈
- void Clear():清除整个栈
实现栈的完整代码
首先提供一个栈接口:
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();
}
链表实现栈
public class LinkedListStack<E> implements Stack<E>, Iterable<E> {
private final int capacity;
private int size;
private final Node<E> head = new Node<>(null, null);
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 head.next == null;
}
@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;
}
};
}
static class Node<E> {
E value;
Node<E> next;
public Node(E value, Node<E> next) {
this.value = value;
this.next = next;
}
}
}
数组实现栈
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;
}
return array[--top];
}
@Override
public E peek() {
if (isEmpty()) {
return null;
}
return array[top-1];
}
@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];
}
};
}
}
题目训练
1、有效的括号
题目
:给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
-
左括号必须用相同类型的右括号闭合。
-
左括号必须以正确的顺序闭合。
-
每个右括号都有一个对应的相同类型的左括号。
提示:
1 <= s.length <= 10^4
s
仅由括号'()[]{}'
组成
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
思路:
- 遇到左括号, 把要配对的右括号放入栈顶
- 遇到右括号, 若此时栈为空, 返回 false,否则把它与栈顶元素对比
- 若相等, 栈顶元素弹出, 继续对比下一组
- 若不等, 无效括号直接返回 false
- 循环结束
- 若栈为空, 表示所有括号都配上对, 返回 true
- 若栈不为空, 表示右没配对的括号, 应返回 false
该方法使用了 ArrayStack 类
/**
* 遇到左括号,把要配对的右括号放入栈顶
* 遇到右括号,把它与栈顶元素对比
* 若相等,栈顶元素弹出,续对比 下一组
* 若不等,无效括号直接返回 false
* @param args
*/
// 测试用例
public static void main(String[] args) {
E01Leetcode20 s = new E01Leetcode20();
System.out.println(s.isValid("([{}])"));
System.out.println(s.isValid("()[]{}"));
System.out.println(s.isValid("()"));
System.out.println("-----------------------");
System.out.println(s.isValid("[)"));
System.out.println(s.isValid("([)]"));
System.out.println(s.isValid("([]"));
System.out.println(s.isValid("("));
System.out.println("-----------------------");
System.out.println(s.isValid(")("));
System.out.println(s.isValid("]"));
}
// 核心代码
public boolean isValid(String s) {
ArrayStack<Character> stack = new ArrayStack<>(s.length());
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() && c == stack.peek()) {
stack.pop();
} else {
return false;
}
}
}
return stack.isEmpty();
}
方法二:
class Solution {
// 栈
// 根据题目分为三种情况:
// ① 已经遍历了所有字符串,但栈不为空,说明有相应的左括号没有右括号来匹配。
// ② 遍历字符串过程中,发现栈里没有要匹配对应的字符串
// ③ 遍历字符串过程中,栈已经空了,没有对应的字符。说明右括号没有对应的左括号
public boolean isValid(String s) {
//初始化栈
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
// 碰到左括号,就把相应的右括号入栈
if (ch == '(') {
deque.push(')');
} else if (ch == '{') {
deque.push('}');
} else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
// .isEmpty 判断栈是否为空 || .peek() 返回栈顶元素
return false;
} else { // 如果是左括号判断是否和栈顶元素匹配
deque.pop();
}
}
// 最后判断栈中的元素是否匹配
return deque.isEmpty();
}
}
2、逆波兰表达式求值
题目
:给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法
表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
提示:
1 <= tokens.length <= 10^4
tokens[i]
是一个算符("+"
、"-"
、"*"
或"/"
),或是在范围[-200, 200]
内的一个整数
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入: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
解题代码:
方法一:
/**
* - 遇到数字压入栈
* - 遇到运算符,就从栈弹出两个数字做运算,将结果压入栈
* - 遍历结束,栈中剩下的数字就是结果
* @param tokens
* @return
*/
// 核心代码
public int evalRPN(String[] tokens) {
LinkedList<Integer> stack = new LinkedList<>();
for (String t: tokens) {
switch (t) {
case "+":{
Integer b = stack.pop();
Integer a = stack.pop();
stack.push(a + b);
break;
}
case "-":{
Integer b = stack.pop();
Integer a = stack.pop();
stack.push(a - b);
break;
}
case "*":
Integer b = stack.pop();
Integer a = stack.pop();
stack.push(a * b);
break;
case "/":
b = stack.pop();
a = stack.pop();
stack.push(a / b);
break;
default:
stack.push(Integer.parseInt(t));
}
}
return stack.pop();
}
// 测试用例
public static void main(String[] args) {
String[] tokens = {"10","6","9","3","+","-11","*","/","*","17","+","5","+"};
System.out.println(new E02Leetcode150().evalRPN(tokens));
tokens = new String[]{"2","1","+","3","*"};
System.out.println(new E02Leetcode150().evalRPN(tokens));
}
方法二:
class Solution {
// 逆波兰表达式:是一种 后缀 表达式,所谓后缀就是指运算符写在后面。
// 平常使用的算式则是一种 中缀 表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
// 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
public int evalRPN(String[] tokens) {
// 逆波兰表达式相当于二叉树中的后序遍历。
// 定义栈
Deque<Integer> deque = new LinkedList();
// 遍历字符串
for (String s : tokens) {
if (s.equals("+")) {
// 将二者之和压入栈中
deque.push(deque.pop() + deque.pop());
} else if (s.equals("-")) {
// 将二者之差压入栈中
// 因为栈是先进后出,所以应该是后出 - 前出。
deque.push(-deque.pop() + deque.pop());
} else if (s.equals("*")) {
// 将二者之积压入栈内
deque.push(deque.pop() * deque.pop());
} else if (s.equals("/")) {
// 将二者之商压入栈内
int temp1 = deque.pop();
int temp2 = deque.pop();
deque.push(temp2 / temp1);
} else {
//
deque.push(Integer.valueOf(s));
}
}
return deque.pop();
}
}
3、中缀表达式转后缀
/*
思路
1. 遇到数字, 拼串
2. 遇到 + - * /
- 优先级高于栈顶运算符 入栈
- 否则将栈中高级或平级运算符出栈拼串, 本运算符入栈
3. 遍历完成, 栈中剩余运算符出栈拼串
- 先出栈,意味着优先运算
4. 带 ()
- 左括号直接入栈
- 右括号要将栈中直至左括号为止的运算符出栈拼串
| |
| |
| |
_____
a+b
a+b-c
a+b*c
a*b+c
(a+b)*c
*/
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();
}
static int priority(char c) {
return switch (c) {
case '(' -> 0;
case '*', '/' -> 2;
case '+', '-' -> 1;
default -> throw new IllegalArgumentException("不合法字符:" + c);
};
}
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 <= x <= 9
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
示例 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
解题代码:
class MyQueue {
Stack<Integer> StackIn;
Stack<Integer> StackOut;
/** 在这里初始化数据结构 */
public MyQueue() {
StackIn = new Stack<>(); // 负责进栈
StackOut = new Stack<>(); // 负责出栈
}
/** 将元素 x 放入队列的尾部 */
public void push(int x) {
// 进栈
StackIn.push(x);
}
/** 从队列首部移除元素并返回该元素。 */
public int pop() {
dumpstackIn();
// 出栈
return StackOut.pop();
}
/** 返回队列首部元素。 */
public int peek() {
dumpstackIn();
// 返回栈顶但不移除元素
return StackOut.peek();
}
/** 返回队列是否为空 */
public boolean empty() {
// StackIn/StackOut 输入输出元素
return StackIn.isEmpty() && StackOut.isEmpty();
}
// 如果 stackOut 为空,那么将 stackIn 中的元素全部放到 stackOut 中
private void dumpstackIn() {
if (!StackOut.isEmpty()) return;
while (!StackIn.isEmpty()) {
StackOut.push(StackIn.pop());
}
}
}
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(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
提示:
1 <= x <= 9
- 最多调用100 次 push、pop、top 和 empty
- 每次调用 pop 和 top 都保证栈不为空
示例:
输入:
["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
解题代码:
class MyStack {
// Deque 接口继承了 Queue 接口
// 所有 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
Deque<Integer> quel;
/** 初始化数据 */
public MyStack() {
quel = new ArrayDeque<>();
}
/** 将元素 x 压入栈顶 */
public void push(int x) {
quel.addLast(x);
}
/** 移除并返回栈顶元素 */
public int pop() {
int size = quel.size();
size--;
// 将 que1 导入 que2 ,但留下最后一个值
while (size-- > 0) {
quel.addLast(quel.peekFirst());
quel.pollFirst();
}
int res = quel.pollFirst();
return res;
}
/** 返回栈顶元素 */
public int top() {
return quel.peekLast();
}
/** 判断栈顶是否为空,为空返回true */
public boolean empty() {
return quel.isEmpty();
}
}
最后
对各位小伙伴有帮助的话,希望可以点赞❤️+收藏⭐,谢谢各位大佬~~🙌🙌🙌