今天主要来学习有关队列的相关知识,包括用数组模拟实现队列基本功能,求区间最大值 / 最小值的单调队列。除了用数组实现之外,两部分的【Java-API】放在每部分的后面。
模拟队列
首先,我们先来了解什么是队列,它的模型是什么样的,有什么特征,再考虑用数组怎么实现队列的基本功能。
什么是队列:
队列是一种特殊的线性表,它允许在一端进行插入操作 ,在另一端进行删除操作。我们可以举个例子来帮助我们理解,队列类比为不能并排站的独木桥,它只能一个一个地从一边进入独木桥,并且按顺序一个一个从另一边出独木桥,不允许按照原路返回。下面的图可以帮助我们理解:
需要实现:
在队尾插入元素,在队头弹出元素
需要什么:
一个指向队头的“指针”:hh;
一个指向队尾的“指针”:tt;
详解:怎么模拟队列
1.插入操作
2.弹出操作
3.判断队列是否为空
4.取出队头元素
题目
题解完整代码(Java)
import java.util.Scanner;
public class Main {
static int N = 100010;
static int[] q = new int[N];
static int hh,tt = -1;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int M = sc.nextInt();
while (M-- > 0){
String s = sc.next();
if(s.equals("push")){
int x = sc.nextInt();
push(x);
}else if(s.equals("pop")){
pop();
}else if(s.equals("empty")){
if(!empty()){
System.out.println("NO");
}else {
System.out.println("YES");
}
}else {
System.out.println(getTop());
}
}
}
//插入
public static void push(int x){
q[++tt] = x;
}
//弹出
public static void pop(){
hh++;
}
//判断队列是否为空
public static boolean empty(){
if(tt >= hh){
return false;
}else {
return true;
}
}
//取出队头元素
public static int getTop(){
return q[hh];
}
}
题目(蓝桥-CLZ银行问题)
解题完整代码(Java)
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Queue<String> VIP = new LinkedList<String>();
Queue<String> Normal = new LinkedList<String>();
int M = sc.nextInt();
for (int i = 0; i < M; i++) {
String[] s = new String[3];
s[0]=sc.next();
if(s[0].equals("IN")){
s[1]=sc.next();
s[2]=sc.next();
if(s[2].equals("N")){
Normal.add(s[1]);
}else {
VIP.add(s[1]);
}
}
if(s[0].equals("OUT")) {
s[1]=sc.next();
if(s[1].equals("N")){
Normal.poll();
}else {
VIP.poll();
}
}
}
while (!VIP.isEmpty()){
System.out.println(VIP.poll());
}
while (!Normal.isEmpty()){
System.out.println(Normal.poll());
}
}
}
Java-API:
【Queue】:
单调队列
单调队列常用于找出滑动区间的最大值 / 最小值
滑动区间几个关注点:
1.什么时候队头的指针向后移动?
2.主要思想:
框住的区间就相当于是一个队列,当我们要找出区间最小值时,只要队列不为空的情况下,如果队尾指针指向的数字值大于准备进队的数字值,队尾指向的数字值就永远不会是区间的最小值,这时把队尾元素弹出,再把准备进队的数字添加进队列,如此循环(最大值只是改变比较的大于小于即可)。
题目
解题完整代码(Java)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
public class Main {
static int N = 1000010;
static int[] a = new int[N];//a[]存原来的值
static int[] q = new int[N];//q[]存队列,数的下标
public static void main(String[] args) throws IOException {
StreamTokenizer re = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
re.nextToken();int n = (int)re.nval;
re.nextToken();int k = (int)re.nval;
for (int i = 0; i < n; i++) {
re.nextToken();a[i] = (int)re.nval;//把数字存入a数组
}
//找出滑动窗口最小值
int hh = 0;//定义队头
int tt = -1;//定义队尾
for (int i = 0; i < n; i++) {
//判断原先的队头是否已经滑出队列,不在窗口的范围里面
if(hh <= tt && i-k+1 > q[hh]){
hh++;
}
//当队列不为空且队尾指向的数字大于a[i]时,队尾指向的数字就永远不会是区间的最小值,把队尾元素弹出
while (hh <= tt && a[q[tt]] >= a[i]){//q[tt]指的是队尾元素在原存值数组a中的下标,a[q[tt]]指的是下标为tt所指向的元素值
tt--;
}
//把i添加进队尾
q[++tt] = i;//此时队尾为下标i
//输出有范围,当框住的数有k个时才满足区间要求,由于i从0开始,所以当i >= k-1(等同于i从1开始记录时,i >= k)时区间才框住了k个数
if(i >= k-1){
System.out.print(a[q[hh]]+" ");//由于队头指向的元素是区间最小值,q[hh]指的是队头元素在原存值数组a中的下标,a[q[hh]]指的是下标为hh所指向的元素值
}
}
System.out.println();
//找出滑动窗口最大值
hh = 0;//定义队头
tt = -1;//定义队尾
for (int i = 0; i < n; i++) {
//判断原先的队头是否已经滑出队列,不在窗口的范围里面
if (hh <= tt && i - k + 1 > q[hh]) {
hh++;
}
//当队列不为空且队尾指向的数字小于a[i]时,队尾指向的数字就永远不会是区间的最大值,把队尾元素弹出
while (hh <= tt && a[q[tt]] <= a[i]) {
tt--;//q[tt]指的是队尾元素在原存值数组a中的下标,a[q[tt]]指的是下标为tt所指向的元素值
}
//把i添加进队尾
q[++tt] = i;//此时队尾为下标i
//输出有范围,当框住的数有k个时才满足区间要求,由于i从0开始,所以当i >= k-1(等同于i从1开始记录时,i >= k)时区间才框住了k个数
if (i >= k - 1) {
System.out.print(a[q[hh]] + " ");//由于队头指向的元素是区间最大值,q[hh]指的是队头元素在原存值数组a中的下标,a[q[hh]]指的是下标为hh所指向的元素值
}
}
}
}
题目(蓝桥-MAX最值差)
解题完整代码(Java)
import java.io.*;
import java.util.*;
public class Main {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args)throws IOException {
String[] s = br.readLine().split(" ");
int N = Integer.parseInt(s[0]);
int k = Integer.parseInt(s[1]);
//a存放原数组
int[] a = new int[N+1];
s = br.readLine().split(" ");
//l存放上一个比它大的元素,r存放下一个比它大的元素
int[] l = new int[N+1];
int[] r = new int[N+1];
for (int i = 1; i <= N; i++) {
a[i] = Integer.parseInt(s[i-1]);
}
//定义双端队列
Deque<Integer> q = new ArrayDeque<>();
//找到区间最小值
for (int i = 1; i <= N; i++) {
if (!q.isEmpty() && q.peekFirst() < i-k){
q.pollFirst();
}
while (!q.isEmpty() && a[q.peekLast()] > a[i]){
q.pollLast();
}
q.addLast(i);
l[i] = q.peekFirst();
}
//清空双端队列
q.clear();
//找到区间最大值
for (int i = 1; i <= N; i++) {
if (!q.isEmpty() && q.peekFirst() < i-k){
q.pollFirst();
}
while (!q.isEmpty() && a[q.peekLast()] < a[i]){
q.pollLast();
}
q.addLast(i);
r[i] = q.peekFirst();
}
int ans = 0;
for (int i = 1; i <= N; i++) {
ans = Math.max(ans,a[r[i]] - a[l[i]]);
}
out.println(ans);
out.flush();
}
}
Java-API:
【Deque】:
在有关队列的学习中,首先了解队列的定义,它的模型,实现的功能,再用数组进行逐一实现。可以和之前的栈进行对比学习,了解哪个模型是实现先进先出,哪个模型是实现先进后出;包括两种单列的情况:单调栈(该元素左/右边第一个比自身大/小的元素/元素下标)和单调队列(滑动区间的最大值/最小值)。