【力扣】栈和队列
在跟随代码随想录刷题的过程中,完成栈和队列部分的相关题目,所以在此进行一个小总结。
本篇笔记涉及的力扣题目如下:【可以使用Ctrl+F进行搜索~】
栈和队列相关问题的一点小总结
- 栈和队列能够相互转换,以解决相对应的问题
- 栈的定义:Stact< T > stack = new Stack<>();队列的定义:队列分为Queue, Deque, PriorityQueue等,一般情况下能够使用Deque(双端队列)解决问题;Deque< T > deque = new LinkedList<>()
- Stack以及Queue的相关操作:Java 实例 - 队列(Queue)用法 ,Java Stack 类
题目:232. 用栈实现队列
解题思路
- 仅使用栈的基本操作实现队列,队列:先进先出
- 新建两个栈用于模拟进队和出队操作
- 需要注意的是,函数dumpstackIn: 当stackOut不为空时,则出队直接从stackOut操作即可【画图模拟可知】
- 如进栈1234,出队列的顺序为1234,若取出1,则需要将4321依次取出放入stackOut,再操作stackOut的栈首元素,即1;此时stackOut中元素为234;
- 入队5,将5放入stackIn中,队首元素出队,即取出元素2,而不用判断刚入栈的元素5
复杂度
- 时间复杂度:从stackIn将元素取出,放入stackOut需要的时间为: O(n)
- 空间复杂度: O(n)
Java代码实现
class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
public MyQueue() {
stackIn = new Stack<>();
stackOut = new Stack<>();
}
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() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
// 将栈stackIn中的元素清空,以满足队列的先进先出
public void dumpstackIn(){
if(!stackOut.isEmpty()) return;
while(!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
}
}
/**
* 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();
*/
参考资料:
题目:225. 用队列实现栈
解题思路
- 设置两个队列,元素主要存储在queue1,queue2为辅助队列,即辅助queue1进行排序,以实现栈的后进先出
- 元素位置的调换主要实现实在元素入栈时,使用交换操作来改变元素的位置
复杂度
-
时间复杂度: 在push操作时,需要移动队列中的元素,故时间复杂度为O(n)
-
空间复杂度: O(n)
Java代码实现
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
queue2.offer(x);
while(!queue1.isEmpty()){
queue2.offer(queue1.poll());
}
// 交换queue1和queue2,将元素均放入queue1中
// 放入一个元素,就将元素的顺序调换位置,放入queue1
Queue<Integer> queueTemp;
queueTemp = queue1;
queue1 = queue2;
queue2 = queueTemp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
public boolean empty() {
return queue1.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();
*/
参考资料:
题目:20. 有效的括号
解题思路
- 匹配括号,需要将左右括号按照顺序依次进行匹配
- 方法1:使用栈进行模拟;方法2:使用双端队列deque时,需要注意获取元素的位置(队头 / 队尾)
- 当遇到左括号,就将相应的右括号进栈;当遇到右括号,就查看栈顶元素是否和当前的右括号相同即可
- 右括号匹配失败的两种情况:1.没有相对应的左括号(栈为空);2.左括号不能够进行匹配(栈顶元素不匹配)
- 最后处理完整个字符串,如果栈不为空,则返回false
- 特殊情况:当给定字符串长度为奇数时,永远不能够进行匹配
Java代码实现 -- stack
class Solution {
public boolean isValid(String s) {
if(s.length() % 2 == 1) {
return false;
}
Stack<Character> stack = new Stack<>();
for(int i=0; i<s.length(); i++) {
if(s.charAt(i) == '(') {
stack.push(')');
} else if(s.charAt(i) == '[') {
stack.push(']');
} else if(s.charAt(i) == '{') {
stack.push('}');
} else if(stack.isEmpty() || stack.pop() != s.charAt(i)) {
// 执行到此,说明遇到了右括号;匹配失败的两种情况:1.没有相对应的左括号(栈为空);2.左括号不能够进行匹配(栈顶元素不匹配)
return false;
}
// else {
// stack.pop();
// }
}
return stack.isEmpty();
}
}
Java代码实现 -- deque
class Solution {
public boolean isValid(String s) {
if(s.length() % 2 == 1) {
return false;
}
Deque<Character> deque = new LinkedList<>();
for(int i=0; i<s.length(); i++) {
if(s.charAt(i) == '(') {
deque.offer(')');
} else if(s.charAt(i) == '[') {
deque.offer(']');
} else if(s.charAt(i) == '{') {
deque.offer('}');
} else if(deque.isEmpty() || deque.peekLast() != s.charAt(i)) {
return false;
} else {
deque.removeLast();
}
}
return deque.isEmpty();
}
}
参考资料
题目:1047. 删除字符串中的所有相邻重复项
解题思路
- 重复项删除操作:删除两个相邻且相同的字母;例如:"abbaca" -> 删除b,"aaca" -> 删除a,"ca"
- 将给定字符串中的元素依次进栈,如果该元素与栈顶元素相同,则进行出栈操作
- 同理,此处也可以使用双端队列(deque)方式
- 最后使用字符串拼接或者StringBuilder方法,将栈中元素依次出栈,并按倒序进行拼接即可
Java代码实现 -- stack+字符串拼接
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack = new Stack<Character>();
for(int i=0; i<s.length();i++){
char ch;
ch = s.charAt(i);
if(stack.isEmpty() || ch != stack.peek()){
// 栈为空或者栈顶元素和ch不相同,则将该元素入栈
stack.push(ch);
} else {
// 否则出栈
stack.pop();
}
}
String str = "";
while(!stack.isEmpty()){
str = stack.pop() + str;
}
return str;
}
}
Java代码实现 -- stack+StringBuilder()
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack = new Stack<>();
for(int i=0; i<s.length(); i++) {
if(stack.isEmpty()) {
stack.push(s.charAt(i));
continue;
}
if(s.charAt(i) != stack.peek()) {
stack.push(s.charAt(i));
} else {
stack.pop();
}
}
StringBuilder sb = new StringBuilder();
while(!stack.isEmpty()) {
sb = sb.insert(0, stack.pop());
}
return sb.toString();
}
}
Java代码实现 -- deque
class Solution {
public String removeDuplicates(String s) {
Deque<Character> deque = new LinkedList<>();
for(int i=0; i<s.length(); i++) {
if(deque.isEmpty()) {
deque.offer(s.charAt(i));
continue;
}
if(s.charAt(i) != deque.peekLast()) {
deque.offer(s.charAt(i));
} else {
deque.removeLast();
}
}
StringBuilder sb = new StringBuilder();
while(!deque.isEmpty()) {
sb = sb.append(deque.pollFirst());
}
return sb.toString();
}
}
参考资料
题目:150. 逆波兰表达式求值
解题思路
- 根据提示可知,该操作适合用栈来解决

- 当遇到数字时,将数字放入栈中;当遇到符号时,从栈顶弹出两个元素,根据相应的符号进行数值计算;并将计算结果放入栈中,重复以上过程即可。
- 细节问题:1.优先弹出的操作数为第二个操作数,对于“-”和“/”操作需要特殊处理;2. 注意tokens数组为["2","1","+","3","*"],每一个元素均为"",为String类型,不是char类型;因而在进行比较时应该使用equals方法,而非==;3.String -> int: Integer.parseInt(String);4.int -> String: String.valueOf(int);
复杂度
- 时间复杂度: 该算法仅遍历了一遍字符串数组,所以时间复杂度为O(n)
- 空间复杂度: O(n)
Java代码实现
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<Integer>();
for(int i=0; i<tokens.length; i++){
if("+".equals(tokens[i]) || "-".equals(tokens[i]) || "*".equals(tokens[i]) || "/".equals(tokens[i])){
int num1 = stack.pop();
int num2 = stack.pop();
if("+".equals(tokens[i])){
stack.push(num1 + num2);
} else if("-".equals(tokens[i])){
stack.push(num2 - num1);
} else if("*".equals(tokens[i])){
stack.push(num1 * num2);
} else {
stack.push(num2 / num1);
}
} else {
stack.push(Integer.parseInt(tokens[i]));
}
}
return stack.pop();
}
}
参考资料
题目:239. 滑动窗口最大值
方法一:自定义队列添加元素方法
解题思路
- queue.add(),在添加元素时,使用queue.getLast()获取队尾元素,若添加元素大于队尾元素,则将队尾元素移除;重复上述过程,使队列中的元素按递减序排列。【当需要取出最大值时,将队首元素取出即可】
- 对于num[0],num[1],num[2],将三个元素依次取出放入queue中
- nums[3]及以后元素,依次从nums[]中取出每个元素,放入队列中
- 操作方法:将nums[i-k]移除队列【此时,需要判断nums[i-k]是否在队列中,在自定义队列操作中实现】,nums[i]进入队列,取出最大元素放入res中。
复杂度
- 时间复杂度: O(n)
- 空间复杂度: O(n)
Java代码实现
class MyQueue {
Deque<Integer> queue = new LinkedList<>();
public void add(int val){
// 添加元素时,将比val小的元素移除,使整个队列元素呈递减状态,队首元素为最大元素
while(!queue.isEmpty() && val > queue.getLast()){
queue.removeLast();
// queue.getLast()获取队尾元素,removeLast()移除队尾元素
}
queue.add(val);
}
public int peek(){
return queue.peek();
}
public void poll(int val){
// 判断当前元素是否在队列中,即是否在add时已去除
if(!queue.isEmpty() && val == queue.peek()){
queue.poll();
}
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
MyQueue queue = new MyQueue();
int len = nums.length - k + 1;
int[] res = new int[len];
int count = 0;
for(int i=0; i<k; i++){
queue.add(nums[i]);
}
res[count++] = queue.peek();
for(int i = k; i < nums.length; i++){
queue.poll(nums[i-k]);
queue.add(nums[i]);
res[count++] = queue.peek();
}
return res;
}
}
方法二:使用双端队列完成
解题思路
- 依次遍历整个数组,使用双端队列记录最大元素的下标,通过下标的进栈和出栈,控制队列中的元素个数
- 保证在[i - k + 1, i] 中选到最大值,需要满足:1. 队头结点的下标应该在[i-k+1, i]范围内,不符合该范围则弹出;2. 保证每次放入的元素需要大于队尾元素;同时满足以上两点,那么将该元素进队即可
- 何时将元素放入res中?队列中的元素是按照递减序列排序的,如果i增长到k-1时,即滑动窗口移动一下,就将队列的队头元素放入res中即可。
Java代码实现
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new LinkedList<>();
int n = nums.length;
// 定义res数组,使用idx控制数组的长度
int[] res = new int[n-k+1];
int idx = 0;
for(int i=0; i<n; i++) {
// 队头结点的下标应该在[i-k+1, i]范围内,不符合该范围则弹出;注意:队列中放置的是nums数组的下标
while(!deque.isEmpty() && deque.peek()<i-k+1) {
deque.poll();
}
// 保证每次放入的元素需要大于队尾元素
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
if(i >= k-1) {
res[idx++] = nums[deque.peek()];
}
}
return res;
}
}
参考资料
题目:347. 前 K 个高频元素
方法一:大顶堆
解题思路
- 将nums数组中的元素依次放入map(数值--出现个数)中,之后再依次取出map中的KV属性对,放入queue中,最后将结果写入res中即可
- 定义优先队列PriorityQueue,PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->b[1]-a[1]),表示根据两属性对的value值进行排序,即根据出现次数进行排序。此处是将出现次数多的放在前面,因而为构建一个大顶堆
- 将map中的结果放入queue中,pq.add(new int[]{ entry.getKey(), entry.getValue() })
- res中仅输出数值,因而仅需要K-V对中的key,即pq.poll()[0]
Java代码实现
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for(int num : nums){
map.put(num, map.getOrDefault(num, 0)+1);
}
PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->b[1]-a[1]);
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
pq.add(new int[]{
entry.getKey(), entry.getValue()
});
}
int res[] = new int[k];
for(int i=0; i<k; i++){
res[i] = pq.poll()[0];
}
return res;
}
}
方法二:小顶堆
解题思路
- 大致过程同上,唯一区别在定义PriorityQueue时为小顶堆
- 定义优先队列PriorityQueue,PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->a[1]-b[1]),表示根据两属性对的value值进行排序,即根据出现次数进行排序。此处是将出现次数少的放在前面,因而为构建一个小顶堆
Java代码实现
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int[] res = new int[k];
// 将nums中的元素依次放入map中
HashMap<Integer, Integer> map = new HashMap<>();
for(int i=0; i<nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0)+1);
}
// System.out.println(map.get(1));
// 定义优先队列,使map中的元素依次进队出队,队列中的元素保持为前k个,构建小顶堆(出现次数:小->大)
PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> a[1] - b[1]);
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++) {
res[i] = pq.poll()[0];
}
return res;
}
}

浙公网安备 33010602011771号