目录
1.队列概念
2.基本方法和简单使用
2.1.基本方法
方法 | 功能 |
boolean offer(E e)
|
入队列
|
E poll()
|
出队列
|
peek()
|
获取队头元素
|
int size()
|
获取队列中有效元素个数
|
boolean isEmpty()
|
检测队列是否为空
|
2.2.简单使用
public static void main(String[] args) {
Queue<Integer> q = new LinkedList<>();
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5); // 从队尾入队列
System.out.println(q.size());
System.out.println(q.peek()); // 获取队头元素
q.poll();
System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
if(q.isEmpty()){
System.out.println("队列空");
}else{
System.out.println(q.size());
}
}
3.模拟实现与应用场景
3.1.各种实现方式分析
实现方式 | 头删法时间复杂度 | 尾插法时间复杂度 |
顺序表 | O(n) | O(1) |
单向链表 | O(1) | O(n) |
含有尾指针的单向链表 | O(1) | O(1) |
3.2.简单实现(含有尾指针的单向链表)
class Node{
public int val;
public Node next;
//构造方法
public Node(int val) {
this.val = val;
}
}
//单向链表实现
public class MyQueue {
public Node front;//队头
public Node rear;//队尾
//入队(尾插法)
public void offer(int val){
Node node = new Node(val);
if(this.front==null){
this.front=node;
this.rear=node;
}
this.rear.next = node;
this.rear = node;
}
public boolean isEmpty(){
if(this.front==null){
return true;
}
return false;
}
//返回队头元素
public int peek(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
return this.front.val;
}
//删除队头元素
public int poll(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
int old = this.front.val;
this.front = this.front.next;
return old;
}
}
3.3.应用场景
3.3.1.循环队列
循环队列底层通常是由数组实现的,常用于生产者消费者模型。
//循环队列
public class MyCircularQueue {
public int front;//队头下标
public int rear;//队尾下标
public int[] elem;
//构造方法,k为队列长度
public MyCircularQueue(int k) {
this.elem = new int[k];//实际有效空间为k-1
//this.elem = new int[k+1];//oj题写法或者使用标记/数组大小变量
}
//入队
public boolean enQueue(int value) {
if(isFull()){
return false;
}
this.elem[this.rear] = value;
this.rear = (this.rear+1)%this.elem.length;
return true;
}
//出队
public boolean deQueue() {
if(isEmpty()){
return false;
}
this.front = (this.front+1)%this.elem.length;
return true;
}
//获取队头元素
public int Front() {
if(isEmpty()){
return -1;
}
return this.elem[this.front];
}
//获取队尾元素
public int Rear() {
if(isEmpty()){
return -1;
}
int index = -1;
if(this.rear == 0){
index = this.elem.length-1;
}else{
index = this.rear-1;
}
return this.elem[index];
}
//是否为空
public boolean isEmpty() {
return this.front == this.rear;
}
//是否为满
public boolean isFull() {
if(this.front == (this.rear+1)%this.elem.length){
return true;
}
return false;
}
}
图解:
3.3.2.用栈实现队列
使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
class MyQueue {
private Stack<Integer> s1;
private Stack<Integer> s2;
public MyQueue() {
this.s1 = new Stack<>();
this.s2 = new Stack<>();
}
//入队
public void push(int x) {
s1.push(x);
}
//出队
public int pop() {
if(empty()){
return -1;
}
if(s2.isEmpty()){
int size = s1.size();
for(int i = 0;i<size;i++){
int x = s1.pop();
s2.push(x);
}
return s2.pop();
}else{
return s2.pop();
}
}
//获取队列开头元素
public int peek() {
if(empty()){
return -1;
}
if(s2.isEmpty()){
int find = -1;
int size = s1.size();
for(int i = 0;i<size;i++){
int x = s1.pop();
find = s2.push(x);
}
return find;
}else{
return s2.peek();
}
}
public boolean empty() {
if(s1.isEmpty()&&s2.isEmpty()){
return true;
}
return false;
}
}
图解:
3.3.3.用队列实现栈
使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
):
import java.util.LinkedList;
import java.util.Queue;
class MyStack {
private Queue<Integer> qu1;
private Queue<Integer> qu2;
public MyStack() {
qu1 = new LinkedList<>();
qu2 = new LinkedList<>();
}
//入栈
public void push(int x) {
if(!qu1.isEmpty()){
qu1.offer(x);
}else if(!qu2.isEmpty()){
qu2.offer(x);
}else{
qu1.offer(x);
}
}
//出栈
public int pop() {
if(empty()){
return -1;
}
//一个队列的size-1个元素转移到另一个队列中,剩下的元素就是要弹出栈的
if(!qu1.isEmpty()){
int size = qu1.size();
for(int i = 0;i<size-1;i++){
int a = qu1.poll();
qu2.offer(a);
}
return qu1.poll();
}else{
int size = qu2.size();
for(int i = 0;i<size-1;i++){
int a = qu2.poll();
qu1.offer(a);
}
return qu2.poll();
}
}
//返回栈顶元素
public int top() {
if(empty()){
return -1;
}
//一个队列的元素转移到另一个队列中,使用变量a记录每个元素,最后一个记录的就是栈顶元素
if(!qu1.isEmpty()){
int a = -1;
int size = qu1.size();
for(int i = 0;i<size;i++){
a = qu1.poll();
qu2.offer(a);
}
return a;
}else{
int a = -1;
int size = qu2.size();
for(int i = 0;i<size;i++){
a = qu2.poll();
qu1.offer(a);
}
return a;
}
}
//栈是否为空
public boolean empty() {
if(qu1.isEmpty() && qu2.isEmpty()){
return true;
}
return false;
}
}
图解:
3.3.4.最小元素栈
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈:
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) {
if(stack.isEmpty()){
stack.push(val);
minStack.push(val);
}else{
stack.push(val);
if(val<=minStack.peek()){
minStack.push(val);
}
}
}
public void pop() {
if(stack.isEmpty()){
return;
}
int x = stack.pop();
if(x==minStack.peek()){
minStack.pop();
}
}
public int top() {
if(stack.isEmpty()){
return -1;
}
return stack.peek();
}
public int getMin() {
if(minStack.isEmpty()){
return -1;
}
return minStack.peek();
}
}
图解:
3.3.5.特定时间范围内最近的请求
写一个 RecentCounter
类来计算特定时间范围内最近的请求:
请你实现 RecentCounter 类:
1.RecentCounter() 初始化计数器,请求数为 0 。
2.int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
class RecentCounter {
private Queue<Integer> requests;
public RecentCounter() {
requests = new LinkedList<>();
}
public int ping(int t) {
requests.offer(t);
while((!requests.isEmpty())&&requests.peek()<(t-3000)){
requests.poll();
}
return requests.size();
}
}
题解:这道题说实话只看题目很难看懂,我借用力扣大佬的通俗解释就是t代表这个员工的工号,每次新员工t加入q公司前先把工号小于t -3000的老家伙都辞退,然后再让t入职,统计q公司现有几个员工。举个例子:第一次ping(1),先将1入队,判断队列中位于[1-3000,1]之间的数据,位于区间外的出队,操作结束此时队列只有1;第二次ping(100),先将100入队,判断队列中位于[100-3000,100]之间的数据,位于区间外的出队,操作结束此时队列有1和100;第二次ping(3002),先将3002入队,判断队列中位于[3002-3000,3002]之间的数据,位于区间外的出队,数据1出队,操作结束此时队列有100和3002......