刷穿剑指offer-Day20-队列I 队列的使用与基础题型!

简介: 刷穿剑指offer-Day20-队列I 队列的使用与基础题型!

队列的介绍


队列(queue)是一种简单、常用的数据结构,在上一章栈的学习中,我们已经提到了队列这种数据结构。

  • 队列: 先入先出
  • 栈:    后入先出

队列的操作和我们日常生活中的排队是很像的,先排队的人先得到服务。

结合生活中的场景,我们应该理解,新来一个人加入排队大军,那么肯定是从队尾开始排起,而出队则是发生在队首。


Python & Java 中的队列


队列分为普通的单向队列双端队列,在Python中一般使用collections方法中内置的deque这种双端队列来解题。

而在Java中接口Queue可以使用LinkedList实现单向队列,ArrayDeque实现双端队列。

操作 Python Java 说明
初始化 from collections import deque<br />queue = deque() Queue<Integer> queue = new LinkedList<>();
插入元素 queue.append(1) queue.add(1); 将元素添加至队尾
删除元素 queue.popleft() 为空时报错 queue.remove(); 为空时报错<br />queue.poll(); 为空时返回null 删除队首的元素
获取队首 queue[0] 为空时报错 queue.element(); 为空时报错<br />queue.peek();为空时返回null 返回队首的元素

队列基础操作就是这些,但和栈一样,虽然操作简单,但是重在解题的思路上。

让我们先通过一道简单的题目,熟悉下它吧。


剑指OfferII041.滑动窗口的平均值


https://leetcode-cn.com/problems/qIsx9U/solution/shua-chuan-jian-zhi-offer-day20-dui-lie-09ber/

难度:简单


题目

给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。

实现 MovingAverage 类:

  • MovingAverage(int size) 用窗口大小 size 初始化对象。
  • double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,
    请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。

提示:

  • 1 <= size <= 1000
  • -10 ^ 5 <= val <= 10 ^ 5
  • 最多调用 next 方法 10 ^ 4 次


示例

输入:
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 -= 出队的数字

当队列未满时,sum += 本次入队的数字

这样就可以通过O(1)的时间去计算平均值了。


解题

Python:

class MovingAverage:
    def __init__(self, size: int):
        self.size = size
        self.queue = deque()
        self.sum = 0
    def next(self, val: int) -> float:
        self.sum += val
        self.queue.append(val)
        if len(self.queue) > self.size:
            self.sum -= self.queue.popleft()
        return self.sum / len(self.queue)

Java:

class MovingAverage {
    private int length;
    private Queue<Integer> queue;
    private double sum = 0;
    public MovingAverage(int size) {
        length = size;
        queue = new LinkedList<>();
        sum = 0;
    }
    public double next(int val) {
        if (queue.size() == length) {
            sum -= queue.remove();
        }
        queue.add(val);
        sum += val;
        return sum / queue.size();
    }
}

下来,我们来看一道比较隐晦的队列题目,反正之前刷这道题的时候,真的没想过用队列解题....分析后才发现队列更高效些。


剑指OfferII042.最近请求次数


https://leetcode-cn.com/problems/H8086Q/solution/shua-chuan-jian-zhi-offer-day18-zhan-ii-de51o/

难度:简单


题目

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。
    确切地说,返回在 [t-3000, t] 内发生的请求数。

保证 每次对 ping 的调用都使用比之前更大的 t 值。

提示:

  • 1 <= t <= 10 ^ 9
  • 保证每次对 ping 调用所使用的 t 值都 严格递增
  • 至多调用 ping 方法 10 ^ 4 次


示例

输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [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


分析

坦白说第一次见到这个题,真的没有往队列上面去想。既然是ping递增的,然后左边界又确定是t - 3000,那不是标准的二分查找么?

创建一个数组,然后每次执行二分即可。

当然这里有个小技巧,既然每次t都是递增的,我们可以初始化一个left,每次更新left,无需每次左边界从0开始二分.

但根据题意分析,我们还可以使用队列来完成这道题目,由于每次获取的都是t - 3000的数值。

所以在加入t后,我们持续判断队首数值,如果小于t- 3000就出队,然后返回队列的长度。


二分解题


Python:

class RecentCounter:
    def __init__(self):
        self.req = []
        self.left = 0
    def ping(self, t):
        self.req.append(t)
        self.left = bisect.bisect_left(self.req, t - 3000, self.left)
        return len(self.req) - self.left


Java:

class RecentCounter {
    private ArrayList<Integer> arr;
    private int left = 0;
    public RecentCounter() {
        arr = new ArrayList<>();
    }
    public int ping(int t) {
        return bisect(t);
    }
    private int bisect(int t){
        arr.add(t);
        int right = arr.size();
        while (left < right) {
            int mid = (right - left) / 2 + left;
            if (arr.get(mid) < t - 3000){
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return arr.size() - left;
    }
}


队列解题


Python:

class RecentCounter:
    def __init__(self):
        self.req = deque()
    def ping(self, t):
        self.req.append(t)
        while self.req[0] < t - 3000:
            self.req.popleft()
        return len(self.req)


Java:

class RecentCounter {
    private Queue<Integer> queue;
    public RecentCounter() {
        queue = new LinkedList<>();
    }
    public int ping(int t) {
        queue.add(t);
        while (queue.peek() < t - 3000) {
            queue.poll();
        }
        return queue.size();
    }
}

这道题使用二分和队列到底哪一种效率高呢?


首先定义 ping调用10 ^ 4 次,即O(n) = 10000,然后计算总执行的时间复杂度。

先来说说空间复杂度,对于空间复杂度,那肯定是二分会高一些,因为队列存在出队的情况。


  • 二分 O(n)
  • 队列 最坏O(3000) 最好O(1) 最好即每次t都比上一次大3000

再来说说时间复杂度:

  • 二分 总的时间复杂度为 O(nlogn),执行N次,每次log(n)。
  • 队列 粗算O(n),最好呢?t每次比上一次大1,O(7000),如果ping调用3000次,最好为O(1).


今天队列的题目就先做到这里,其实这些操作并不是队列最擅长的。明天我们将深入学习队列这个数据结构的擅长题目!




相关文章
|
Oracle NoSQL 关系型数据库
实时计算 Flink版操作报错之报错:java.lang.ClassNotFoundException: io.debezium.connector.common.RelationalBaseSourceConnector,如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
9月前
|
监控 Linux 数据处理
Linux grep技巧 结合awk查询
结合 `grep` 和 `awk`,可以实现灵活、高效的文本处理和数据分析。`grep` 用于快速过滤符合条件的行,`awk` 用于进一步处理和提取数据。这种组合使用在日志分析、数据处理和系统监控等场景中尤为常见。掌握这两者的基本用法和组合技巧,可以大大提升在 Linux 环境下的工作效率。
243 7
|
5月前
|
机器学习/深度学习 编解码 监控
《解锁图像“高清密码”:超分辨率重建之路》
图像超分辨率重建技术旨在将低分辨率图像转化为高分辨率图像,恢复更多细节与清晰度。传统方法如插值法、重建模型和稀疏编码虽有一定效果,但受限于复杂度或灵活性。深度学习兴起后,基于卷积神经网络(CNN)、递归神经网络(RNN)及生成对抗网络(GANs)的方法大幅提升了重建质量,如SRCNN、DRCN、SRGAN等模型实现更精细的纹理还原。该技术广泛应用于安防监控、医学成像、遥感领域及影视修复,为各行业提供更清晰的视觉体验。未来,随着技术发展,其潜力将进一步释放,让模糊图像焕发高清光彩。
107 2
|
自然语言处理 数据处理
情感分析的终极形态:全景式细粒度多模态对话情感分析基准PanoSent
【9月更文挑战第24天】PanoSent是一种全新的多模态对话情感分析框架,旨在全景式地提取和分析情感元素,包括情感六元组提取与情感翻转分析两大任务。此框架依托大规模、高质量的多模态数据集PanoSent,涵盖文本、图像、音频等多种模态及多种语言,适应不同应用场景。为解决这些任务,研究人员提出了Chain-of-Sentiment推理框架,结合多模态大语言模型Sentica,实现细粒度的情感分析。尽管PanoSent在情感分析任务上表现优异,但仍面临多模态数据处理和跨领域适用性的挑战。
335 2
|
10月前
|
存储 Java 开发者
成功优化!Java 基础 Docker 镜像从 674MB 缩减到 58MB 的经验分享
本文分享了如何通过 jlink 和 jdeps 工具将 Java 基础 Docker 镜像从 674MB 优化至 58MB 的经验。首先介绍了选择合适的基础镜像的重要性,然后详细讲解了使用 jlink 构建自定义 JRE 镜像的方法,并通过 jdeps 自动化模块依赖分析,最终实现了镜像的大幅缩减。此外,文章还提供了实用的 .dockerignore 文件技巧和选择安全、兼容的基础镜像的建议,帮助开发者提升镜像优化的效果。
|
测试技术 开发工具 虚拟化
iOS自动化测试方案(一):MacOS虚拟机保姆级安装Xcode教程
这篇文章提供了一份保姆级的教程,指导如何在MacOS虚拟机上安装Xcode,包括环境准备、基础软件安装以及USB扩展插件的使用,以实现iOS自动化测试方案的第一步。
1054 0
iOS自动化测试方案(一):MacOS虚拟机保姆级安装Xcode教程
|
JavaScript 前端开发 API
【入门】你连Babel都不会配?那插件不成乱装了
大家好,我是小鑫同学。一位从事过Android开发、混合开发,现在长期从事前端开发的编程爱好者,我觉得在编程之路上最重要的是知识的分享,所谓三人行必有我师。所以我开始在社区持续输出我所了解到、学习到、工作中遇到的各种编程知识,欢迎有想法、有同感的伙伴加我fe-xiaoxin微信交流~
499 0
|
JavaScript
在Vue中,如何使用事件总线来传递数据和触发事件?
在Vue中,如何使用事件总线来传递数据和触发事件?
205 0
|
存储 SQL 定位技术
数据库基础(五):存储过程与触发器的创建、执行、修改、删除
数据库基础(五):存储过程与触发器的创建、执行、修改、删除
433 1
|
JavaScript 前端开发 测试技术
PhantomJS
PhantomJS 是一个基于 WebKit 的无头浏览器,它可以在不显示浏览器界面的情况下执行网页自动化任务。PhantomJS 使用 JavaScript 作为编程语言,并提供了丰富的 API 来操作网页。它支持多种操作系统,如 Windows、macOS 和 Linux 等。
262 2

热门文章

最新文章