队列基本知识
特点:先入后出
队列的应用
如果某个问题满足插入和删除操作满足先入后出的特点时,那么可以考虑使用队列存储
Q41:滑动窗口的平均值
题目(简单):给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。
示例:
inputs = ["MovingAverage", "next", "next", "next", "next"]
inputs = [[3], [1], [10], [3], [5]]
输出:
[null, 1.0, 5.5, 4.66667, 6.0]
解释:
MovingAverage movingAverage = new MovingAverage(3);
movingAverage.next(1); // 返回 1.0 = 1 / 1
movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2
movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3
movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3
解题思路
先入列,并计算sum,队列长度超过滑动窗口宽度时出列一个数,sum减去出列那个数
class MovingAverage {
private Queue<Integer> nums;
private int capacity;
private int sum;
/** Initialize your data structure here. */
public MovingAverage(int size) {
nums = new LinkedList<>();
capacity = size;
}
public double next(int val) {
nums.offer(val);
sum += val;//保存前面数字的和
if(nums.size() > capacity){//超了再出列
sum -= nums.poll();
}
return (double) sum / nums.size();
}
}
Q42:最近请求次数
题目(简单):写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请实现 RecentCounter 类:
RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值
示例:
输入:
inputs = ["RecentCounter", "ping", "ping", "ping", "ping"]
inputs = [[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]
解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3
class RecentCounter {
private Queue<Integer> times;
private int windowSize;
public RecentCounter() {
times = new LinkedList<>();
windowSize = 3000;
}
public int ping(int t) {
times.offer(t);
while (times.peek() < t - windowSize){
times.poll();
}
return times.size();
}
}
Q43:二叉树的广度优先搜索
层序遍历
利用队列,从二叉树的根结点开始,先把根结点放入一个队列之中,每次从队列中取出一个节点遍历。如果该节点有左右子节点,则分别将他们添加到队列中,重复此过程实现遍历
广度优先搜索代码
public List<Integer> bfs(TreeNode root){
Queue<TreeNode> queue = new Queue<>();
while (root !=null){
queue.offer(root);
}
List<Integer> result = new ArrayList<>();
while (!queue.isEmpty()){
TreeNode node = queue.poll();
result.add(node.val);
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
return result;
}
Q43:在完全二叉树中添加节点
题目(中等):完全二叉树是每一层(除最后一层外)都是完全填充(即,节点数达到最大,第 n 层有 2n-1 个节点)的,并且所有的节点都尽可能地集中在左侧。
设计一个用完全二叉树初始化的数据结构 CBTInserter,它支持以下几种操作:
CBTInserter(TreeNode root) 使用根节点为 root 的给定树初始化该数据结构;
CBTInserter.insert(int v) 向树中插入一个新节点,节点类型为 TreeNode,值为 v 。使树保持完全二叉树的状态,并返回插入的新节点的父节点的值;
CBTInserter.get_root() 将返回树的根节点。
示例 1:
输入:inputs = ["CBTInserter","insert","get_root"], inputs = [[[1]],[2],[]]
输出:[null,1,[1,2]]
示例 2:
输入:inputs = ["CBTInserter","insert","insert","get_root"], inputs = [[[1,2,3,4,5,6]],[7],[8],[]]
输出:[null,3,4,[1,2,3,4,5,6,7,8]]
解题思路
从根结点开始遍历输入完全二叉树,如果一个节点的左右子节点都已经存在,就不可能再在这个节点添加新的子节点。因此可以从队列中删除这个节点
class CBTInserter {
private Queue<TreeNode> queue;
private TreeNode root;
public CBTInserter(TreeNode root) {
this.root = root;
queue = new LinkedList<>();
queue.offer(root);
while(queue.peek().left != null && queue.peek().right != null){
TreeNode node = queue.poll();
queue.offer(node.left);
queue.offer(node.right);
}
}
public int insert(int v) {
TreeNode node = queue.peek();
TreeNode newNode = new TreeNode(v);
if(node.left == null){
node.left = newNode;
}else{
node.right = newNode;
//左右子节点已经满了,因此可以从队列中删除这个节点
queue.poll();
queue.offer(node.left);
queue.offer(node.right);
}
return node.val;
}
public TreeNode get_root() {
return this.root;
}
}
Q44:二叉树中每层的最大值
题目(中等):给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
示例1:
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]
解释:
1
/ \
3 2
/ \ \
5 3 9
示例2:
输入: root = [1,2,3]
输出: [1,3]
解释:
1
/ \
2 3
示例3:
输入: root = [1]
输出: [1]
示例4:
输入: root = [1,null,2]
输出: [1,2]
解释:
1
\
2
示例5:
输入: root = []
输出: []
解题思路
利用两个队列,一个队列出列该层的节点;另一个队列入列下一层的节点
public List<Integer> largestValues(TreeNode root) {
Queue<TreeNode> queue1 = new LinkedList<>();
Queue<TreeNode> queue2 = new LinkedList<>();
if(root != null){
queue1.offer(root);
}
List<Integer> result = new LinkedList<>();
int max = Integer.MIN_VALUE;
while(!queue1.isEmpty()){
TreeNode node = queue1.poll();
max = Math.max(max,node.val);
if(node.left != null){
queue2.offer(node.left);
}
if(node.right != null){
queue2.offer(node.right);
}
if(queue1.isEmpty()){
result.add(max);
max = Integer.MIN_VALUE;
queue1 = queue2;
queue2 = new LinkedList<>();
}
}
return result;
}
Q45:二叉树最低层最左边的值
题目(中等):给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
解题思路
用bottomLeft保存每层的最左边值,遍历完成的时候就得到了底层最左边的值
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue1 = new LinkedList<>();
Queue<TreeNode> queue2 = new LinkedList<>();
queue1.offer(root);
int bottomLeft = root.val;
while(!queue1.isEmpty()){
TreeNode node = queue1.poll();
if(node.left != null){
queue2.offer(node.left);
}
if(node.right != null){
queue2.offer(node.right);
}
if(queue1.isEmpty()){
queue1 = queue2;
queue2 = new LinkedList<>();
if(!queue1.isEmpty()){
bottomLeft = queue1.peek().val;
}
}
}
return bottomLeft;
}
Q46:二叉树的右侧视图
题目(中等):给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例 1:
public List<Integer> rightSideView(TreeNode root) {
List<Integer> view = new LinkedList<>();
if(root == null){
return view;
}
Queue<TreeNode> queue1 = new LinkedList<>();
Queue<TreeNode> queue2 = new LinkedList<>();
queue1.offer(root);
while(!queue1.isEmpty()){
TreeNode node = queue1.poll();
if(node.left != null){
queue2.offer(node.left);
}
if(node.right != null){
queue2.offer(node.right);
}
if(queue1.isEmpty()){
view.add(node.val);
queue1 = queue2;
queue2 = new LinkedList<>();
}
}
return view;
}
小结
- 经常被用来实现二叉树的广度优先搜索
- 首先将根结点放入队列
- 从队列取出节点遍历
- 若有子节点,就将子节点放入队列
- 重复以上过程
- 区分不同层有两种方法
- 用两个变量来表示当前层和下一层节点的数目
- 用两个队列分别存放当前层和下一层的节点