算法-栈和队列
1. 用栈实现队列(LeetCode 232)
题目:请你仅使用两个栈实现队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
- void push(int x) 将元素 x 推到队列的末尾
- int pop() 从队列的开头移除并返回元素
- int peek() 返回队列开头的元素
- boolean empty() 如果队列为空,返回 true ;否则,返回 false
思路:
- 用stackin和stackout两个栈模拟队列
- 在dumpstackin时,需要先确认stackout是否为空;只有stackout为空时才能将stackin中的内容压入
java:
- Stack常用函数:
push()
,pop()
,empty()
,peek()
(返回栈顶元素,但不删除)
class MyQueue {
// 用两个栈模拟队列
Stack<Integer> stackin;
Stack<Integer> stackout;
public MyQueue() {
stackin = new Stack<>();
stackout = new Stack<>();
}
public void push(int x) {
stackin.push(x);
}
// 将stackin中的元素全部放到stackout中
private void dumpstackin(){
//stackout清空后才能继续压入,否则顺序会出现问题
if(!stackout.empty()) return;
while (!stackin.empty()){
stackout.push(stackin.pop());
}
}
//移除并返回队列开头元素
public int pop() {
dumpstackin();
return stackout.pop();
}
//返回队列开头元素
public int peek() {
dumpstackin();
return stackout.peek();
}
public boolean empty() {
return stackin.empty() && stackout.empty();
}
}
2. 用队列模拟栈(LeetCode 225)
题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
- void push(int x) 将元素 x 压入栈顶。
- int pop() 移除并返回栈顶元素。
- int top() 返回栈顶元素。
- boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
思路:唯一需要处理的就是pop()
和peek()
。把que1弹出的只剩最后一个元素,即为栈顶元素。
java:
- 单向列表接口
Queue
:实现类LinkedList
- 双向列表接口
Deque
:实现类ArrayDeque
Deque
可以使用addLast()
,pollFirst
,peekFirst()
可以等效单向队列的add()
,poll()
,peek()
class MyStack {
Deque<Integer> que1 = new ArrayDeque<>();
Deque<Integer> que2 = new ArrayDeque<>();
public MyStack() {
}
public void push(int x) {
que1.addLast(x);
}
public int pop() {
while(que1.size() > 1){
que2.addLast(que1.pollFirst());
}
int res = que1.pollFirst();
que1 = que2;
que2 = new ArrayDeque<>();
return res;
}
public int top() {
while(que1.size() > 1){
que2.addLast(que1.pollFirst());
}
int res = que1.pollFirst();
que2.addLast(res);
que1 = que2;
que2 = new ArrayDeque<>();
return res;
}
public boolean empty() {
return (que1.size() == 0);
}
}
3. 有效的括号(LeetCode 20)
题目:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
思路:用栈实现括号匹配
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(ch == ')' && !stack.empty() && stack.peek() == '(')
stack.pop();
else if(ch == ']' && !stack.empty() && stack.peek() == '[')
stack.pop();
else if(ch == '}' && !stack.empty() && stack.peek() == '{')
stack.pop();
else return false;
}
}
return stack.empty();
}
}
4. 删除字符串中的所有相邻重复项(LeetCode 1047)
题目:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
思路:本题和括号匹配类似。最后需要将栈中的内容输出,构建一个新的字符串。
java: stack.size()
获取栈的大小
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack = new Stack<>();
for(int i = 0; i<s.length(); ++i){
char ch = s.charAt(i);
if(!stack.empty() && ch == stack.peek())
stack.pop();
else
stack.push(ch);
}
int stack_size = stack.size();
char[] chars = new char[stack_size];
for(int i = stack_size - 1; i >= 0; --i){
chars[i] = stack.pop();
}
return new String(chars);
}
}
5. 逆波兰表达式求值(LeetCode 150)
题目:给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为 '+'、'-'、'*' 和 '/' 。
- 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
注意:减法和除法的操作数顺序需要注意。第2次pop()得到的此时第1个操作数
java:
- 单引号表示字符
char
,双引号表示字符串String
- 将字符串转化为数值
Integer.valueOf(str)
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(int i = 0; i<tokens.length; ++i){
if(tokens[i].equals("+"))
stack.push(stack.pop() + stack.pop());
else if(tokens[i].equals("-"))
stack.push( -stack.pop() + stack.pop());
else if(tokens[i].equals("*"))
stack.push(stack.pop() * stack.pop());
else if(tokens[i].equals("/")){
int temp1 = stack.pop();
int temp2 = stack.pop();
stack.push(temp2 / temp1);
} else{
stack.push(Integer.valueOf(tokens[i]));
}
}
return stack.pop();
}
}
6. 滑动窗口最大值
题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
思路:
- 暴力解法:时间复杂度
n*k
- 单调队列:时间复杂度
<=2n
, 空间复杂度k
- 本题实现的是单调递减队列(元素值单调递减,但队列存储的是下标)
- 队首元素即为窗口内最大值的下标
- 每次从队尾入队,都要把小于nums[i]的元素下标从队列中清除(符合单调递减)
- for循环后的第一个while循环是确保队列中的下标都在当前窗口内
- 本题使用的是双端队列
ArrayDeque
实现的peekFirst()
,peekLast()
addLast()
,addFirst()
pollFirst()
,pollLast()
isEmpty()
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
int[] result = new int[nums.length - k + 1];
int result_index = 0;
// 队列中存放的是下标,i表示滑动窗口的最右端
for(int i = 0; i<nums.length; ++i) {
// 把不在窗口内的队首元素弹出
while(!deque.isEmpty() && deque.peek() < i-k+1){
deque.pollFirst();
}
// 把队尾小于nums[i]的元素,都从队尾弹出
while(!deque.isEmpty() && nums[i] > nums[deque.peekLast()]){
deque.pollLast();
}
// 把下标i加入队尾
deque.addLast(i);
// 从i到达第一个窗口的最右侧开始,每个循环填充一个窗口最大值
if(i >= k-1){
result[result_index] = nums[deque.peekFirst()];
result_index++;
}
}
return result;
}
}
7. 前K个高频元素(LeetCode 347)
题目:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
思路:
- top-K的问题适合使用堆来实现
- 优先队列
PriorityQueue
的底层数据结构就是堆 - 如果需要自定义排序的规则需要
- 实现比较器
MyComparator implements Comparator<...>
- 重写
public int compare()
函数 - 在创建 优先队列对象 时将 自定义的比较器对象 传入
- 实现比较器
- 本题维护1个大小为k的小根堆,保存当前频数最大的k个[num, num_count]
java:
- 利用HashMap进行常用的频数统计
HashMap<Integer, Integer> map = new HashMao<>(); for(int num: nums){ map.put(i, map.getOrDefault(i, 0) + 1); }
- 快速创建数组
new int[]{num1, num2, ...}
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer,Integer> map = new HashMap<>();
int[] result = new int[k];
// 统计频数
for(int i: nums){
map.put(i, map.getOrDefault(i, 0) + 1);
}
// pq中的每个元素形如:[num, num_count]
// 根据频度排序的、大小为k的小根堆
PriorityQueue<int[]> pq = new PriorityQueue<>(new MyComparator());
// 维护小根堆
for(Map.Entry<Integer, Integer> entry: map.entrySet()){
if(pq.size() < k){
pq.add(new int[]{entry.getKey(), entry.getValue()});
} else{
// 频数大于堆顶频数的,才需要加入堆中
if(entry.getValue() > pq.peek()[1]){
pq.poll();
pq.add(new int[]{entry.getKey(), entry.getValue()});
}
}
}
for(int i = 0; i<k; ++i){
result[i] = pq.poll()[0];
}
return result;
}
}
// 重写比较器Comparator中的compare函数
class MyComparator implements Comparator<int[]>{
@Override
public int compare(int[] pair1, int[] pair2){
//根据频数进行比较
return pair1[1] - pair2[1];
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)