写在前边的话
今天要开启新的知识啦!那就是栈和队列,对于栈和队列感觉仅仅是知道原理,但是在python和java中的表现形式以及具体的使用,感觉自己还是没在脑海里形成体系,就借训练算法的这次机会梳理梳理吧。今天是第一天加油呀!
栈与队列的基础知识
python中的栈和队列
python语言本身是没有内置栈和队列的数据类型,但可以通过其它数据结构来模拟这两种数据结构的行为。
栈
栈遵循后进先出的原则。
栈的基本操作
- 压栈(push):向栈顶添加一个元素
- 弹栈(Pop):从栈顶移除一个元素
- 查看栈顶(Peek):查看但步移除栈顶元素
- 判断是否为空(IsEmpty):判断栈中是否还有元素
- 获取栈的大小(Size):返回栈中元素数量
栈的实现
1 . 使用列表实现栈
class Stack:
def __init__(self):
self.items = []
def is_empty(self):
return not bool(self.items)
def push(self, itme):
self.items.append(itme)
def pop(self):
if not self.is_empty():
return self.items.pop()
def peek(self):
if not self.is_empty():
return self.items[-1]
def size(self):
return len(self.items)
注意:空列表pop()以及获取某个下标值会报错,因此在实现栈时需要先检查下是否为空列表才能pop和peek 。
2. 使用标准库提供的collection.deque类实现栈
collection.deque(双端队列)是一种容器数据结构,两端都可以进行插入和删除操作,因此既可以用作也可以用作队列。
- append(x):在右侧添加元素x
- appendleft(x):在左侧添加元素x
- pop():从右侧移除并返回一个元素
- popleft():从左侧移除并返回一个元素
from collections import deque
class DequeStack:
def __init__(self):
self.items = deque()
def is_empty(self):
return not bool(self.items)
def push(self, itme):
self.items.append(itme)
def pop(self):
if not self.is_empty():
return self.items.pop()
def peek(self):
if not self.is_empty():
return self.items[-1]
def size(self):
return len(self.items)
3. 使用queue.LifoQueue实现栈
queue.LifoQueue是专门用于实现栈的数据结构。queue.LifoQueue是标准库queue中的一个类,可以创建栈。LifoQueue类继承自queue.Queue,因此它具有队列的基本特性,例如线程安全和阻塞行为。
创建LifoQueue(可以指定队列的最大大小,默认为无限大)
import queue
lifo_queue = queue.LifoQueue(maxsize=3) # 最大容量为3
lifo_queue = queue.LifoQueue() # 容量无限大
基本操作
- put(itme):向队列中放入一个元素。如果队列已满,则会阻塞直到有空闲空间。
- put_nowait(item):向队列中放入一个元素。如果队列已满则抛出queue.Full异常。
- get():从队列中获取并移除一个元素。如果队列为空,则会阻塞直到有可用元素。
- get_nowait():从队列中获取并移除一个元素。如果队列为空则抛出queue.Empty异常。
- qsize():返回队列中的元素数量。
- empty():队列空返回True,否则False。
- full():队列满返回True,否则False。
示例
import queue
lifo_queue = queue.LifoQueue(maxsize=3)
# 放入元素
lifo_queue.put(1)
lifo_queue.put(2)
lifo_queue.put(3)
# 放入第四个元素,队列已满则会阻塞
try:
lifo_queue.put(4, timeout=1)
except queue.Full:
print("Queue is full")
# 取元素
print(lifo_queue.get()) # 输出3
print(lifo_queue.get()) # 输出2
print(lifo_queue.get()) # 输出1
# 从空队列中取元素,会阻塞
try:
print(lifo_queue.get(timeout=1))
except queue.Empty:
print("Queue is empty")
注意:
- LifoQueue的put和get都是线程安全的,可以在多线程环境中安全使用
- 当队列已满或者为空时,如果使用put和get的时候没有设置timeout参数,则会阻塞。
- 使用put_nowait或get_nowait可以避免阻塞,但需要处理queue.Full或queue.Empty异常。
总结
queue.LifoQueue是一个线程安全的栈实现,非常适合在多线程环境中使用。如果不用考虑线程安全问题,就可以使用collections.deque。
队列
栈遵循先进先出的原则。
队列的基本操作
- 入队(enqueue):将元素添加到队列的尾部。
- 出队(dequeue):从队列的头部移除并返回一个元素。
- 获取队列首元素:查看队列第一个元素而不是移除它。
- 获取队列大小
- 检查队列是否为空
队列的实现
1. 使用列表
class ListQueue:
def __init__(self):
self.items = []
def is_empty(self):
return not bool(self.items)
def enqueue(self, itme):
self.items.append(itme)
def dequeue(self):
if not self.is_empty():
return self.items.pop(0)
def front(self):
if not self.is_empty():
return self.items[0]
def size(self):
return len(self.items)
注意:pop(0)效率比较低,因为需要移动所有后续的元素。
2. 使用colections.deque
from collections import deque
class DequeQueue:
def __init__(self):
self.items = deque()
def is_empty(self):
return not bool(self.items)
def enqueue(self, itme):
self.items.append(itme)
def dequeue(self):
if not self.is_empty():
return self.items.popleft()
def front(self):
if not self.is_empty():
return self.items[0]
def size(self):
return len(self.items)
3. 使用queue.PriorityQueue
优先级队列,可设置元素出队的优先级顺序。
也是线程安全的,可用在多线程环境中。
基本操作
- put(item, priority=None):向队列中放入一个带有优先级的元素。如果没有指定优先级,默认为0。
- get( block=True, timeout=None):从队列中获取并移除一个元素。队列空,阻塞直到有可用元素,除非设置了block=False。
- qsize():返回队列中大小
- empty():判空,为空True,非空False。
- full():队列满True,否则False。
示例
import queue
pq = queue.PriorityQueue()
# 入队
pq.put((1, 'task1')) # 优先级1
pq.put((3, 'task3')) # 优先级2
pq.put((2, 'task2')) # 优先级3
# 出队
while not pq.empty():
task_priority, task = pq.get()
print(f"Priority task: {task} with priority: {task_priority}")
# 输出
# Priority task: task1 with priority: 1
# Priority task: task2 with priority: 2
# Priority task: task3 with priority: 3
4. 使用queue.Queue
线程安全,可用在多线程环境中。
import queue
class ThreadSafeQueue:
def __init__(self, maxsize = 0):
self.q = queue.Queue(maxsize = maxsize)
def is_empty(self):
return self.q.empty()
def enqueue(self, itme):
self.q.put(itme)
def dequeue(self):
if not self.is_empty():
return self.q.get()
def front(self):
try:
return self.q.queue[0]
except IndexError:
return None
def size(self):
return self.q.qsize()
Java中的栈和队列
栈
1. 使用java.util.Stack
java.util.Stack是java标准库中的一个类,继承自Vector,因此它是同一个同步的类(内部已经实现了线程安全机制)。基本操作,压栈(push)、弹栈(pop)、查看栈顶元素(peek)等。
示例
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
// 创建一个栈
Stack<Integer> stack = new Stack<>();
// 入栈 (push)
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println("Stack: " + stack);
// 查看栈顶元素 (peek)
int topElement = stack.peek();
System.out.println("Top element: " + topElement);
// 出栈 (pop)
int poppedElement = stack.pop();
System.out.println("Popped element: " + poppedElement);
System.out.println("Stack after pop: " + stack);
// 检查栈是否为空 (isEmpty)
boolean isEmpty = stack.isEmpty();
System.out.println("Is stack empty? " + isEmpty);
// 获取栈的大小 (size)
int size = stack.size();
System.out.println("Stack size: " + size);
}
}
注意:
pop和peek在栈为空时会抛出EmptyStackException异常,所以调用之前要先判空。
2. 使用java.util.Deque
ArrayDeque(底层是循环数组)
java.util.Deque(双端队列),ArrayDeque是一个实现了Deque接口的类,双端操作,可以实现栈。
示例
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeStackExample {
public static void main(String[] args) {
// 创建一个 Deque 作为栈
Deque<Integer> stack = new ArrayDeque<>();
// 入栈 (push)
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println("Stack: " + stack);
// 查看栈顶元素 (peek)
Integer topElement = stack.peek();
System.out.println("Top element: " + topElement);
// 出栈 (pop)
Integer poppedElement = stack.pop();
System.out.println("Popped element: " + poppedElement);
System.out.println("Stack after pop: " + stack);
// 检查栈是否为空 (isEmpty)
boolean isEmpty = stack.isEmpty();
System.out.println("Is stack empty? " + isEmpty);
// 获取栈的大小 (size)
int size = stack.size();
System.out.println("Stack size: " + size);
}
}
注意:
java.util.ArrayDeque是非同步的,要注意线程安全问题。
LinkedList(底层是双向链表)
java.util.Deque(双端队列),LinkedList是一个实现了Deque接口的类,双端操作,可以实现栈,这个实现也是非同步的。
import java.util.Deque;
import java.util.LinkedList;
public class Stack {
public static void main(String[] args) {
// 创建一个 Deque 实例作为栈
Deque<Integer> stack = new LinkedList<>();
// 使用 push 方法压入元素到栈顶
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println("Stack: " + stack);
// 使用 pop 方法弹出栈顶元素
Integer topElement = stack.pop();
System.out.println("Popped element: " + topElement);
// 查看新的栈顶元素
Integer newTopElement = stack.peek(); // peek() 不会移除栈顶元素
System.out.println("New top element: " + newTopElement);
// 打印更新后的栈
System.out.println("Updated stack: " + stack);
}
}
队列
1. 使用 java.util.LinkedList 模拟队列
LinkedList主要是为了实现列表而设计的,但它的两端操作非常高效,可以很好地模拟队列的行为。
示例
import java.util.LinkedList;
import java.util.Queue;
public class LinkedListQueueExample {
public static void main(String[] args) {
// 创建一个 LinkedList 作为队列
Queue<Integer> queue = new LinkedList<>();
// 入队 (enqueue)
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println("Queue: " + queue);
// 出队 (dequeue)
Integer removed = queue.poll();
System.out.println("Removed element: " + removed);
System.out.println("Queue after poll: " + queue);
// 获取队首元素 (peek)
Integer firstElement = queue.peek();
System.out.println("First element: " + firstElement);
// 检查队列是否为空 (isEmpty)
boolean isEmpty = queue.isEmpty();
System.out.println("Is queue empty? " + isEmpty);
// 获取队列大小 (size)
int size = queue.size();
System.out.println("Queue size: " + size);
}
}
2. 使用 java.util.Queue 接口的实现
java.util.Queue接口定义了队列的基本操作。java.util.ArrayDeque是一个实现了 Queue 接口的类,提供了高效的两端操作。
示例
import java.util.ArrayDeque;
import java.util.Queue;
public class ArrayDequeQueueExample {
public static void main(String[] args) {
// 创建一个 ArrayDeque 作为队列
Queue<Integer> queue = new ArrayDeque<>();
// 入队 (enqueue)
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println("Queue: " + queue);
// 出队 (dequeue)
Integer removed = queue.poll();
System.out.println("Removed element: " + removed);
System.out.println("Queue after poll: " + queue);
// 获取队首元素 (peek)
Integer firstElement = queue.peek();
System.out.println("First element: " + firstElement);
// 检查队列是否为空 (isEmpty)
boolean isEmpty = queue.isEmpty();
System.out.println("Is queue empty? " + isEmpty);
// 获取队列大小 (size)
int size = queue.size();
System.out.println("Queue size: " + size);
}
}
注意:
在 Java 中,java.util.Queue 接口定义了两种主要的方法来处理入队和出队操作:offer和 poll与 add和 remove。这两种方法在行为上有一些差异,特别是当队列已满或为空时。
- offer: 将指定的元素添加到此队列中,如果队列已满,则返回 false。不会抛异常。
- poll:从此队列中检索并移除队首元素,如果此队列为空,则返回 null不会抛出异常。
- add: 将指定的元素添加到此队列中,如果队列已满,则抛出IllegalStateException。如果队列不允许指定元素(例如,不允许 null元素),则抛NullPointerException。
- remove:从此队列中检索并移除队首元素,如果此队列为空,则抛出出NoSuchElementException。 如果队列不允许指定元素(例如,不允许 null元素),则抛出 NullPointerException。
3. 使用 java.util.PriorityQueue
java.util.PriorityQueue 是一个优先级队列的实现,它按照元素的优先级顺序出队。优先级队列通常用于需要根据元素的重要程度来决定处理顺序的情况。
示例
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
// 创建一个优先级队列
PriorityQueue<Integer> queue = new PriorityQueue<>();
// 入队 (enqueue)
queue.offer(3);
queue.offer(1);
queue.offer(2);
System.out.println("Queue: " + queue);
// 出队 (dequeue)
Integer removed = queue.poll();
System.out.println("Removed element: " + removed);
System.out.println("Queue after poll: " + queue);
// 获取队首元素 (peek)
Integer firstElement = queue.peek();
System.out.println("First element: " + firstElement);
// 检查队列是否为空 (isEmpty)
boolean isEmpty = queue.isEmpty();
System.out.println("Is queue empty? " + isEmpty);
// 获取队列大小 (size)
int size = queue.size();
System.out.println("Queue size: " + size);
}
}
232.用栈实现队列
题目链接
题目难度
简单
看到题目的第一想法
使用两个栈,一个作为输入栈,一个作为输出栈。
看完代码随想录以后的总结
解题思路
1. 使用两个栈,一个作为输入栈,一个作为输出栈。
文章讲解
视频讲解
代码编写
python
class MyQueue:
def __init__(self):
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
self.stack_in.append(x)
def pop(self) -> int:
if self.empty():
return None
if self.stack_out:
return self.stack_out.pop()
else:
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
x = self.pop()
self.stack_out.append(x)
return x
def empty(self) -> bool:
return not (self.stack_in or self.stack_out)
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
- 时间复杂度: push和empty为O(1), pop和peek为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() {
if(empty()){
return -1;
}
if(!stackOut.isEmpty()){
return stackOut.pop();
}else{
while(!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
}
return stackOut.pop();
}
public int peek() {
int x = pop();
stackOut.push(x);
return x;
}
public boolean empty() {
return stackIn.isEmpty() && stackOut.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();
*/
- 时间复杂度: push和empty为O(1), pop和peek为O(n)
- 空间复杂度: O(n)
225. 用队列实现栈
题目链接
题目难度
简单
看到题目的第一想法
第一次看到题目的时候,由于对队列和栈使用不熟悉其实是没想到比较好的办法来解决这个题目的。
看完代码随想录以后的总结
解题思路
1. 使用两个队列
pop的时候,将deque_in中n-1(n为队列的大小)元素依次出队列并放到deque_out中;将deque_in中剩下的元素出队列作为返回值;交换deque_in和deque_out。
2. 使用一个队列
pop的时候,将队列中的n-1个元素依次出队列并添加到队列尾部,然后再pop出元素作为返回值。
文章讲解
视频讲解
代码编写
python
from collections import deque
# 使用两个队列
class MyStack:
def __init__(self):
self.deque_in = deque()
self.deque_out = deque()
def push(self, x: int) -> None:
self.deque_in.append(x)
def pop(self) -> int:
if self.empty():
return None
n = len(self.deque_in)
for i in range(n-1):
self.deque_out.append(self.deque_in.popleft())
re = self.deque_in.popleft()
self.deque_in, self.deque_out = self.deque_out, self.deque_in
return re
def top(self) -> int:
re = self.pop()
self.deque_in.append(re)
return re
def empty(self) -> bool:
return not self.deque_in
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
- 时间复杂度:push和empty为O(1), pop和top为O(n)
- 空间复杂度:O(n)
from collections import deque
# 使用1个队列
class MyStack:
def __init__(self):
self.deque = deque()
def push(self, x: int) -> None:
self.deque.append(x)
def pop(self) -> int:
if self.empty():
return None
n = len(self.deque)
for i in range(n-1):
self.deque.append(self.deque.popleft())
return self.deque.popleft()
def top(self) -> int:
re = self.pop()
self.deque.append(re)
return re
def empty(self) -> bool:
return not self.deque
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
- 时间复杂度:push和empty为O(1), pop和top为O(n)
- 空间复杂度:O(n)
java
// 使用两个队列
class MyStack {
Queue<Integer> queue_in;
Queue<Integer> queue_out;
public MyStack() {
queue_in = new LinkedList<>();
queue_out = new LinkedList<>();
}
public void push(int x) {
queue_in.offer(x);
}
public int pop() {
if(empty()){
return -1;
}
int n = queue_in.size();
for(int i=0; i< n - 1; i++)
{
queue_out.offer(queue_in.poll());
}
int re = queue_in.poll();
Queue<Integer> tmp = queue_out;
queue_out = queue_in;
queue_in = tmp;
return re;
}
public int top() {
int x = pop();
queue_in.offer(x);
return x;
}
public boolean empty() {
return queue_in.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();
*/
- 时间复杂度:push和empty为O(1), pop和top为O(n)
- 空间复杂度:O(n)
// 使用一个队列
class MyStack {
Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
queue.offer(x);
}
public int pop() {
if(empty()){
return -1;
}
int n = queue.size();
for(int i=0; i< n - 1; i++)
{
queue.offer(queue.poll());
}
int re = queue.poll();
return re;
}
public int top() {
int x = pop();
queue.offer(x);
return x;
}
public boolean empty() {
return queue.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();
*/
- 时间复杂度:push和empty为O(1), pop和top为O(n)
- 空间复杂度:O(n)
20. 有效的括号
题目链接
题目难度
简单
看到题目的第一想法
看到题目的第一想法就是使用栈来解题,然后用的是列表来实现,另外用字典保存括号对。但是在写代码过程中一直调试不通过,由于列表中存放的是与遍历括号相匹配的另一半,所以在进行比对的时候需要考虑很多中情况,感觉还是梳理的不太好:
- 当前栈如果为空的话,那么如果当前括号是另一半的话直接返回False
- 如果当前栈不为空,那么如果比对的括号是另一半的话那么就要跟栈底元素相同;如果不是另一半那么就将当前括号的另一半入栈。
- 最后要判断栈是否为空,如果为空了那么就匹配完了否则返回False。
python
class Solution:
def isValid(self, s: str) -> bool:
if len(s) <= 1:
return False
stack = []
dic = {"(": ")", "{": "}", "[": "]"}
for i in range(0, len(s)):
if not stack: # 空了
if s[i] in dic:
stack.append(dic[s[i]])
continue
else:
return False
# 不空
if s[i] == stack[-1]:
stack.pop()
else:
if s[i] in dic:
stack.append(dic[s[i]])
else:
return False
return True if not stack else False
- 时间复杂度:O(n)
- 空间复杂度:O(n)
看完代码随想录以后的总结
解题思路
以什么时候不能匹配来分情况:
1. 字符串里左方向的括号多余了 ,eg:"(()[]"。这种情况已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false。
2. 字符串括号没有多余,但是匹配不上。这种情况遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false。
3. 字符串里右方向的括号多余了,eg: "()[]]"。这种情况遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false。
文章讲解
视频讲解
代码编写
python
class Solution:
def isValid(self, s: str) -> bool:
if len(s) <= 1:
return False
stack = []
dic = {"(": ")", "{": "}", "[": "]"}
for c in s:
if c in dic:
stack.append(dic[c])
elif not stack or c != stack[-1]:
return False
else:
stack.pop()
return True if not stack else False
- 时间复杂度:O(n)
- 空间复杂度:O(n)
java
class Solution {
public boolean isValid(String s) {
int n = s.length();
if(n % 2 == 1){
return false;
}
Map<Character, Character> map = new HashMap<>();
map.put('(', ')');
map.put('{', '}');
map.put('[', ']');
Deque<Character> stack = new LinkedList<>();
for(int i=0; i<n; i++){
char ch = s.charAt(i);
if(map.containsKey(ch)){
stack.push(map.get(ch));
}else if( stack.isEmpty() || ch != stack.peek()){
return false;
}else{
stack.pop();
}
}
return stack.isEmpty();
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
1047. 删除字符串中的所有相邻重复项
题目链接
题目难度
简单
看到题目的第一想法
看到题目的第一想法就是使用栈来存储字符,并检查当前字符与栈尾是否相同,相同的话就出栈,否则就入栈。
python
class Solution:
def removeDuplicates(self, s: str) -> str:
res = []
for c in s:
if not res or c != res[-1]:
res.append(c)
else:
res.pop()
return ''.join(res)
- 时间复杂度:O(n)
- 空间复杂度:O(n)
看完代码随想录以后的总结
解题思路
1.看了代码随想录看到思路是一样的。就是使用栈,然后遍历字符串与栈尾相同则出栈,否则入栈。
文章讲解
视频讲解
代码编写
python同上
java
class Solution {
public String removeDuplicates(String s) {
Deque<Character> stack = new LinkedList<>();
char ch;
for(int i=0; i<s.length(); i++){
ch = s.charAt(i);
if( stack.isEmpty() || ch != stack.peek() ){
stack.push(ch);
}else{
stack.pop();
}
}
String res = "";
while(!stack.isEmpty()){
res = stack.pop() + res;
}
return res;
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
今日总结
今天是栈和队列训练的第一天,对于python中的栈和队列还是相对比较熟悉,但是java不熟,包括包含哪些java中的栈和队列以及各有什么优势,感觉今天学的不是特别好,打算明天再梳理一下。算法方面没什么大问题。