剑指Offer(专项突破版)刷题笔记 | 第七章 队列

182 阅读5分钟

队列基本知识

特点:先入后出

队列的应用

如果某个问题满足插入和删除操作满足先入后出的特点时,那么可以考虑使用队列存储

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:

``` 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] ``` 示例 2: ``` 输入: [1,null,3] 输出: [1,3] ``` 示例 3: ``` 输入: [] 输出: [] ```
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;
}

小结

  • 经常被用来实现二叉树的广度优先搜索
    • 首先将根结点放入队列
    • 从队列取出节点遍历
    • 若有子节点,就将子节点放入队列
    • 重复以上过程
  • 区分不同层有两种方法
    • 用两个变量来表示当前层和下一层节点的数目
    • 用两个队列分别存放当前层和下一层的节点