【Java 数据结构】队列与OJ题
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
我是作者亦是读者。
目录
4.1 设计循环队列 (来源:LeetCode 难度:中等)
4.2 用队列实现栈 (来源:LeetCode 难度:简单)
4.3 用栈实现队列 (来源:LeetCode 难度:简单)
1、什么是队列
队列只允许在一端进行插入数据操作在另一端进行删除数据操作的特殊线性表队列具有先进先出FIFO(First In First Out)
入队列进行插入操作的一端称为队尾
出队列进行删除操作的一 端称为队头
队列也可以数组和链表的结构实现使用链表的结构实现更优一些因为如果使用数组的结构 出队列在数组头上出数据效率会比较低。
2、初识Queue
2.1 认识一下Queue
如图可见Queue本质上是一个接口被Deque(双端队列)继承被LinkedList实现所以Queue底层是通过链表来实现的而Queue是不能实例化对象的 所以我们想使用队列则需要new一个LinkedeList对象实现向上转型这样就可以使用Queue中的方法了
add 和 offer 都是入队列这两个区别是当使用add时一些队列有大小限制如果想在一个满的队列中加入新的元素时调用 add() 方法就会抛出一个 unchecked 异常而调用 offer() 方法会返回 false。因此就可以在程序中进行有效的判断
2.2 简单使用下Queue
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println(queue.peek()); //获取对头元素 -> 1
System.out.println(queue.poll()); //出队头元素 -> 1
System.out.println(queue.peek()); //获取对头元素 -> 2
System.out.println(queue.isEmpty()); //判断队列是否为null -> false
queue.clear(); //清空队列
System.out.println(queue.isEmpty()); //判断队列是否为null -> true
}
3、模拟实现Queue
3.1 构造方法和成员属性
public class MyQueue {
private class Node {
private int val; //数据域
private Node next; //指针域名
private Node(int val) {
this.val = val;
}
}
//队头入队尾出
private Node head; //队头引用
private Node last; //队尾引用
private int size; //队列有效数据个数
}
3.2 offer 方法
// 队尾入队列
public boolean offer(int val) {
Node newNode = new Node(val);
// 如果是第一次入队列
if (this.head == null) {
this.head = newNode;
this.last = newNode;
} else {
this.last.next = newNode;
this.last = this.last.next;
}
this.size++;
return true;
}
3.3 poll 方法
// 队头出队列
public int poll() {
Node node = this.head;
// 如果队列为null
if (this.head == null) {
throw new MyQueueIsEmptyException("队列为空");
} else {
this.head = this.head.next;
}
this.size--;
return node.val;
}
3.4 peek 方法
// 取队头元素
public int peek() {
// 如果队列为null
if (this.head == null) {
throw new MyQueueIsEmptyException("队列为空");
} else {
return this.head.val;
}
}
至于size和empty方法就交给各位小伙伴实现了由于有了前面链表的基础队列实现起来是比较简单的所以更多希望阅读文章的初学者能下来多自己手写以下画画图。
4、队列相关的OJ题
4.1 设计循环队列 (来源:LeetCode 难度:中等)
题目设计你的循环队列实现。 循环队列是一种线性数据结构其操作表现基于 FIFO先进先出原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里一旦一个队列满了我们就不能插入下一个元素即使在队列前面仍有空间。但是使用循环队列我们能使用这些空间去存储新的值。
你的实现应该支持如下操作
MyCircularQueue(k): 构造器设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空返回 -1 。
Rear: 获取队尾元素。如果队列为空返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
解题思路对于这道题来说我们有很多种解题思路但我们要注意一点如何区分队列为空和队列满的情况这里可以添加size属性记录也可使用标记也可也空一个位置出来区分那么今天我们就空一个位置那么如何区分队列空和队列满呢见下图
对于循环队列的实现我们采用的时数组方案有front和rear分别存储队头下标和队尾元素的后一个下标。至于更多需要注意细节的地方比如修正front和rear的位置时在代码中有对应的注释查看即可
public class MyCircularQueue {
private int array[]; //存放数据的数组
private int front; // 队头下标
private int rear; // 队尾下标
public MyCircularQueue(int k) {
this.array = new int[k + 1]; //因为我们要浪费一个空间所以需要多开辟一个空间
}
// 入队列
public boolean enQueue(int value) {
// 如果队列满的情况
if (isFull()) {
return false;
}
// 没有满就往队尾入元素
this.array[this.rear] = value;
// rear往前走一步需要空出一个位置所以当rear走到length-1时需要修正下rear
this.rear = (this.rear + 1) % this.array.length;
return true;
}
// 出队列
public boolean deQueue() {
// 如果队列为null的情况
if (isEmpty()) {
return false;
}
// 从队头出队列需要修正队头的位置
this.front = (this.front + 1) % this.array.length;
return true;
}
// 取队头元素
public int Front() {
// 如果队列为null的情况
if (isEmpty()) {
return -1;
}
return this.array[this.front]; //返回队头元素
}
// 取队尾元素
public int Rear() {
// 如果队列为null的情况
if (isEmpty()) {
return -1;
}
// 如果rear为0的情况我们需要特殊处理
int index = this.rear == 0 ? this.array.length - 1 : this.rear - 1;
return this.array[index]; //返回队尾元素
}
// 判断队列是否为空
public boolean isEmpty() {
// 当front和rear相遇了则表示队列中没有元素
return this.front == this.rear;
}
// 判断队列是否满了
public boolean isFull() {
// 因为我们会浪费一个空间所以rear+1等于front就满了
// 但是我们要rear防止越界所以要进行修正(this.rear + 1) % this.array.length
return (this.rear + 1) % this.array.length == this.front;
}
}
4.2 用队列实现栈 (来源:LeetCode 难度:简单)
题目请你仅使用两个队列实现一个后入先出LIFO的栈并支持普通栈的全部四种操作push、top、pop 和 empty。
实现 MyStack 类
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的返回 true 否则返回 false 。
解题思路这道题的解题思路并不难我们只需要定义两个队列入栈的时候往不为null的队列入出栈的时候先将不为空的队列的size()-1个元素全部放到为空的队列中剩余最后一个元素直接出栈即可由于取栈顶元素不用删除元素所以剩余的最后一个元素还要放入另一个队列中至于更详细的内容可以配合下面代码和注释阅读
class MyStack {
private Queue<Integer> qu1;
private Queue<Integer> qu2;
public MyStack() {
this.qu1 = new LinkedList<>();
this.qu2 = new LinkedList<>();
}
public void push(int x) {
// 两个队列都为null的情况
if (this.qu1.isEmpty() && this.qu2.isEmpty()) {
this.qu1.offer(x);
return;
}
// 哪个队列不为null往哪个队列中入元素
if (!this.qu1.isEmpty()) {
this.qu1.offer(x);
} else {
this.qu2.offer(x);
}
}
public int pop() {
// 如果两个队列都为空的情况下就不能出队操作
if (empty()) {
return -1;
}
// 先将不为空的队列的size-1个元素全部放到为空的队列中
if (!this.qu1.isEmpty()) {
while (qu1.size() - 1 != 0) {
qu2.offer(qu1.poll());
}
return qu1.poll(); //返回最后一个元素
} else {
while (qu2.size() - 1 != 0) {
qu1.offer(qu2.poll());
}
return qu2.poll(); //返回最后一个元素
}
}
public int top() {
// 如果队列为null
if (empty()) {
return -1;
}
int ret = 0; //保留剩余最后一个栈顶元素的变量
// 先将不为空的队列的size-1个元素全部放到为空的队列中
if (!this.qu1.isEmpty()) {
while (qu1.size() - 1 != 0) {
qu2.offer(qu1.poll());
}
ret = qu1.peek();
qu2.offer(qu1.poll());
} else {
while (qu2.size() - 1 != 0) {
qu1.offer(qu2.poll());
}
ret = qu2.peek();
qu1.offer(qu2.poll()); //取栈顶元素不能删除掉还得入另一个队列中去
}
return ret;
}
public boolean empty() {
return this.qu1.isEmpty() && this.qu2.isEmpty(); //两个队列都为空栈才为空
}
}
4.3 用栈实现队列 (来源:LeetCode 难度:简单)
题目请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作push、pop、peek、empty。
实现 MyQueue 类
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空返回 true 否则返回 false
解题思路这道题我们可以这样做定义两个栈分别是pushStack和popStack入队统一都入到pushStack栈中出队头元素都从popStack中出如果popStack是空的情况就先将pushStack栈中所有的元素放入popStack中再出栈顶元素。 至于判断队列是否为空需要满足 pushStack和popStack都为空队列才为空
class MyQueue {
private Stack<Integer> pushStack;
private Stack<Integer> popStack;
public MyQueue() {
this.pushStack = new Stack<>();
this.popStack = new Stack<>();
}
public void push(int x) {
// 入队列都在pushStack中
this.pushStack.push(x);
}
public int pop() {
// 出队列都从popStack出
if (popStack.empty()) {
// 把pushStack栈中的元素都放到popStack栈中
while (!pushStack.empty()) {
popStack.push(pushStack.pop());
}
}
if (!popStack.empty()) {
return popStack.pop();
} else {
return -1;
}
}
public int peek() {
// 取队头元素都从popStack中取
if (popStack.empty()) {
// 把pushStack栈中的元素都放到popStack栈中
while (!pushStack.empty()) {
popStack.push(pushStack.pop());
}
}
if (!popStack.empty()) {
return popStack.peek();
} else {
return -1;
}
}
public boolean empty() {
return pushStack.empty() && popStack.empty();
}
}
4.4 最小栈 (来源:LeetCode 难度:中等)
题目设计一个支持 push pop top 操作并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
解题思路这道题一看就需要用到两个栈一个栈入数据一个栈始终压入最小值如何操作呢这里我们可以定义两个栈分别是stack和minStack入栈的时候入stack这个栈如果是第一次入栈则当前入栈元素为最小值同时也需要入minStack栈中如果后续入栈元素小于或等于minStack栈顶元素则也需要入minStack栈如果比minStack栈顶元素大则不需要入minStack栈了出栈的时候判断stack栈如果与minStack栈元素相等则minStack也要出栈顶元素
public class MinStack {
private Stack<Integer> stack;
private Stack<Integer> minStack;
public MinStack() {
this.stack = new Stack<>();
this.minStack = new Stack<>();
}
public void push(int val) {
// 如果stack为null表示第一次入栈
// 此时入栈的元素也是此时栈中最小值也要入minStack栈
if (this.stack.isEmpty()) {
this.stack.push(val);
this.minStack.push(val);
return;
}
this.stack.push(val);
// 如果stack栈顶元素小于等于minStack栈顶元素则也需要入minStack栈中
if (this.stack.peek() <= this.minStack.peek()) {
this.minStack.push(val);
}
}
// 出栈
public void pop() {
if (this.stack.isEmpty()) {
return;
}
// 如果出栈元素与minStack元素相等minStack也要出栈
if (this.stack.pop().equals(this.minStack.peek())) {
this.minStack.pop();
}
}
// 取栈顶元素
public int top() {
if (this.stack.isEmpty()) {
return -1;
} else {
return this.stack.peek();
}
}
// 取栈中最小元素
public int getMin() {
if (this.stack.isEmpty()) {
return -1;
} else {
return this.minStack.peek();
}
}
}
下期预告【Java 数据结构】二叉树