文章目录
队列的基础知识:先进先出
队列Queue是一种内部元素特征符合”先进先出“的数据集合。如果解决问题时遇到数据的删除和插入操作满足"先进先出"的特点,那么可以考虑用队列来存储这些数据。
- 常用的API
操作 | 无异常保护 | 有异常保护 |
---|---|---|
向队尾添加一个元素 | add(node) | offer(node ) |
从队首删除一个元素 | remove(node) | poll(node) |
返回最前面一个元素 | element() | peek() |
Java中常用的队列实现类有LinkedList, ArrayDeque, PriorityQueue.但是PriorityQueue不是队列数据结构,而是一个堆。
应用
滑动窗口
剑指 Offer II 041. 滑动窗口的平均值
请实现如下类型MovingAverage,计算滑动窗口中所有数字的平均值,该类型构造函数的参数确定滑动窗口的大小,每次调用成员函数next时都会在滑动窗口中添加一个整数,并返回滑动窗口中所有数字的平均值。
解题思路
本题主要分成两个部分:
- 维护滑动窗口:
当添加完窗口的元素后,如果窗口元素数目大于限制数目时,需要删除最早添加的那个元素。
这符合先入先出的特性。所以我们可以考虑用队列来作为数据集合来维护这个滑动窗口。 - 计算平均值:
计算窗口元素的和,只需要每次加上一个元素的值,然后减去删除元素的值即可,时间复杂度为O(1)。
具体代码如下所示:
代码
class MovingAverage {
private Queue<Integer> queue;
private int capacity = 0;
private int sum = 0;
/** Initialize your data structure here. */
public MovingAverage(int size) {
this.capacity = size;
queue = new LinkedList<>();
}
public double next(int val) {
queue.offer(val);
sum += val;
if(queue.size() > this.capacity) {
sum -= queue.poll();
}
return (double)sum / queue.size();
}
}
剑指 Offer II 042. 最近请求次数
题目:请实现如下类型RecentCounter,它是统计过去3000ms内的请求次数的计数器。该类型的构造函数RecentCounter初始化计数器,请求数初始化为0;函数ping(int t)在时间t添加一个新请求(t表示以毫秒为单位的时间),并返回过去3000ms内(时间范围为[t-3000,t])发生的所有请求数。假设每次调用函数ping的参数t都比之前调用的参数值大。
解题思路
1.选择数据结构:题目要求3000毫秒到当前时间t内的请求数,那么我们需要用一个数据容器保存历史请求的时间点,同时删除位于区间【3000-t,t]外的时间点。
2.滑动窗口:由于统计的逻辑,符合先入先出的特点,所以可用队列作为数据集合,维护一个滑动窗口,每次ping新的请求,向前移动窗口。
具体代码如下:
代码
class RecentCounter {
private Queue<Integer> times;
private int windowSize;
public RecentCounter() {
windowSize = 3000;
times = new LinkedList<>();
}
public int ping(int t) {
times.offer(t);
while((times.peek() + windowSize) < t) {
times.poll();
}
return times.size();
}
}
/**
* Your RecentCounter object will be instantiated and called as such:
* RecentCounter obj = new RecentCounter();
* int param_1 = obj.ping(t);
*/
广度优先搜索
解题技巧:如果二叉树的面试题,遇到层这个概念,可以尝试运用广度优先搜索来解决此类问题。
广度优先搜索遍历的步骤:
- 定义队列,把root存入队列。
- 遍历当前层。根据当前队列长度,得到当前层的节点数,开始遍历当前层的节点。
- 记录下一层节点,在遍历当前层节点时候,我们可以把节点的下一层左右节点保存到队列中。
剑指 Offer II 043. 往完全二叉树添加节点
题目:完全二叉树是每一层(除最后一层外)都是完全填充(即,节点数达到最大,第 n 层有 2n-1 个节点)的,并且所有的节点都尽可能地集中在左侧。
设计一个用完全二叉树初始化的数据结构 CBTInserter,它支持以下几种操作:
CBTInserter(TreeNode root) 使用根节点为 root 的给定树初始化该数据结构;
CBTInserter.insert(int v) 向树中插入一个新节点,节点类型为 TreeNode,值为 v 。使树保持完全二叉树的状态,并返回插入的新节点的父节点的值;
CBTInserter.get_root() 将返回树的根节点。
解题思路
解决此题要掌握两个知识点
- 完全二叉树的特点
一种二叉树,以从上到下,从左到右的顺序添加节点。 - 广度优先搜索,找到可以添加节点的父节点。
用过队列来实现按层遍历二叉树,遍历到节点的左或右子节点为空时,则找到可添加节点的指针了。
具体实现如下:
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class CBTInserter {
private Queue<TreeNode> queue;
private boolean isLeft;
private TreeNode root;
public CBTInserter(TreeNode root) {
queue = new ArrayDeque<>();
this.root = root;
queue.offer(root);
//1. 初始化队列,找到可插入子节点的父节点。
while(!queue.isEmpty()) {
TreeNode node = queue.peek();
if(node.left == null) {
isLeft = true;
break;
} else {
queue.offer(node.left);
}
if(node.right == null) {
isLeft = false;
break;
} else {
queue.offer(node.right);
queue.poll();//左右都满了,那么此节点肯定不能插入子节点了,所以把它从队列移除。
}
}
}
public int insert(int v) {
//queue的队首节点即为待插入的父节点
TreeNode parent = queue.peek();
TreeNode newNode = new TreeNode(v);
if(isLeft) {
parent.left = newNode;
} else {
parent.right = newNode;
queue.poll();
isLeft = false;
}
isLeft = !isLeft;
queue.offer(newNode);
return parent.val;
}
public TreeNode get_root() {
return root;
}
}
/**
* Your CBTInserter object will be instantiated and called as such:
* CBTInserter obj = new CBTInserter(root);
* int param_1 = obj.insert(v);
* TreeNode param_2 = obj.get_root();
*/
剑指 Offer II 044. 二叉树每层的最大值
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
解题思路
如果遇到二叉树中有层的问题,可以尝试运用广度优先搜索来遍历二叉树,计算每一层的最大值。
分成两步:
- 定义数据容器队列,遍历二叉树。
- 统计每一层的最大值。
使用for循环遍历每一层,与此同时可把下一层节点入队。
具体代码如下:
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> largestValues(TreeNode root) {
if(root == null) {
return new ArrayList<Integer>();
}
//对二叉树,进行层序遍历
//找出每层最大值
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> result = new ArrayList();
queue.offer(root);
while(!queue.isEmpty()) {
int max = Integer.MIN_VALUE;
int size = queue.size();//每层节点个数
for(int i = 0; i < size; i++) {
TreeNode node = queue.poll();
max = node.val > max ? node.val : max;//统计每层的最大值
if(node.left !=null ) queue.offer(node.left);//记录下一层节点
if(node.right !=null ) queue.offer(node.right);//记录下一层节点
}
result.add(max);
}
return result;
}
}
总结
队列是是一种先入先出的数据结构,一般会应用在处理滑动窗口问题和广度优先搜索问题。当遇到二叉树层的问题,可以尝试使用广度优先搜索遍历。