算法之栈
栈
介绍
栈是一种线性结构,它只能从一端添加元素,也只能从一端取出元素(这一端称之为栈顶)。
Stack这种数据结构用途很广泛,在计算机的使用中,大量的运用了栈,比如编译器中的词法分析器、Java虚拟机、软件中的撤销操作(Undo)、浏览器中的回退操作,编译器中的函数调用实现等等。
接口 | 说明 | 复杂度 |
---|---|---|
void push(E e) | 向栈中加入元素 | O(1) 均摊 |
E pop() | 弹出栈顶元素 | O(1) 均摊 |
E peek() | 查看栈顶元素 | O(1) |
int getSize() | 获取栈中元素个数 | O(1) |
boolean isEmpty() | 判断栈是否为空 | O(1) |
说明:push和pop操作在最后面进行,有可能触发resize,但均摊来算是O(1)的。
面试题
有效的括号
给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串,判断字符串是否有效。
说明:有效字符串需满足
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
示例1
输入:()
输出:true
示例2
输入:()[]{}
输出:true
示例3
输入:(]
输出:false
示例4
输入:([)]
输出:false
示例5
输入:{[]}
输出:true
解题思路
这道题让我们验证输入的字符串是否为括号字符串,包括大括号,中括号和小括号。
这里我们使用栈。
- 遍历输入字符串
- 如果当前字符为左半边括号时,则将其压入栈中
- 如果遇到右半边括号时,分类讨论:
- 1)如栈不为空且为对应的左半边括号,则取出栈顶元素,继续循环
- 2)若此时栈为空,则直接返回 false
- 3)若不为对应的左半边括号,反之返回 false
代码实现
public static 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.isEmpty()) {
return false;
}
char topChar = stack.pop();
if (ch == ')' && topChar != '(') {
return false;
} else if (ch == ']' && topChar != '[') {
return false;
} else if (ch == '}' && topChar != '{') {
return false;
}
}
}
return stack.isEmpty();
}
用两个栈实现队列
用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。
解题思路
in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。
- push 元素时,始终是进入栈,pop 和 peek 元素时始终是走出栈。
- pop 和 peek 操作,如果出栈为空,则需要从入栈将所有元素移到出栈,也就是调换顺序,比如开始push的顺序是 3-2-1,1 是最先进入的元素,则到出栈的顺序是 1-2-3,那 pop 操作拿到的就是 1,满足了先进先出的特点。
- pop 和 peek 操作,如果出栈不为空,则不需要从入栈中移到数据到出栈。
代码实现
static Stack<Integer> stack1 = new Stack<>();
static Stack<Integer> stack2 = new Stack<>();
public static void appendTail(int item) {
stack1.push(item);
}
public static int deleteHead() {
if (!stack2.isEmpty()) {
return stack2.pop();
}
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
return stack2.pop();
}
栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
解题思路
借用一个辅助的栈,遍历压栈顺序,先将 第一个放入栈中,这里是 1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是 4,很显然 1≠4 ,所以需要继续压栈,直到相等以后开始出栈。
出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
代码实现
public static boolean isPopOrder(int[] pPush, int[] pPop) {
boolean bPossible;
if (pPush.length == 0 || pPop.length == 0) {
return false;
}
Stack<Integer> stack = new Stack<>();
int popIndex = 0;
for (int push : pPush) {
stack.push(push);
while (!stack.isEmpty() && stack.peek() == pPop[popIndex]) {
stack.pop();
++popIndex;
}
}
bPossible = stack.isEmpty() && pPop.length == popIndex;
return bPossible;
}
包含 min 函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的 min 函数(时间复杂度应为O(1))。
解题思路
使用两个 stack,一个作为数据栈,另一个作为辅助栈。其中 数据栈 用于存储所有数据,而 辅助栈 用于存储最小值。
- 入栈的时候:首先往空的数据栈里压入数字 3 ,此时 3 是最小值,所以把最小值压入辅助栈。接下来往数据栈里压入数字 4 。由于 4 大于之前的最小值,因此只要入数据栈,此时辅助栈把记录最小值栈的栈顶元素再压一次(辅助栈保持跟数据站一样的元素数量)。
- 出栈的时候:数据栈进行了弹栈操作,那么记录最小值元素的辅助栈同样也进行弹栈操作即可。
- 获得栈顶元素的时候:直接返回数据栈的栈顶元素。
- 栈最小元素:直接返回辅助栈的栈顶元素,但是不要弹出。
代码实现
static Stack<Integer> data = new Stack<>();
/**
* 辅助栈,data栈中每次进入一个元素,min辅助栈就存放当前data栈中的最小元素
*/
static Stack<Integer> min = new Stack<>();
public static void push(int item) {
//直接调用data的push方法,将元素压入栈中。
data.push(item);
//如果进栈的元素小于当前的最小值就把进栈元素的值赋值该minValue
if (min.size() == 0 || item < min.peek()) {
min.push(item);
} else {
//将当前的最小值压入minStack栈中
min.push(min.peek());
}
}
public static void pop() {
if (data.size() > 0 && min.size() > 0) {
data.pop();
min.pop();
}
}
public static int min() {
if (data.size() > 0 && min.size() > 0) {
return min.peek();
}
return 0;
}
public static int top() {
if (data.size() > 0) {
return data.peek();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!