题目:最近的请求次数
写一个 RecentCounter 类来计算特定时间范围内最近的请求。 请实现 RecentCounter 类:
- RecentCounter() 初始化计数器,请求数为 0 。
- int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/H8086Q
// 输入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],返回 1recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3
理解题意
输入时间数量,单位为ms,返回在当前时间内会发出的请求数量。
比如说输入1,那么因为范围在 [t - 3000, t]之间,范围就是[-2999, 1],包含了1;所以请求一次
接着输入100,那么范围在[-2900, 100],因为包含了上次输入的1和本次的100; 所以请求次数为两次
- 保证每次对ping的调用都使用比之前更大的t值
- 每个测试用例会使用严格递增的t值来调用ping,最多调用1000次ping
- 下一次输入的时间的大小一定会大于上一次输入时间,也就是说t是一个递增的关系
每次请求都会执行ping方法,该方法返回t时间点及此前3000ms内的请求总次数(包含t时间点和t-3000时间点)
数据结构及算法思维的选择
基本解法及编码的实现
暴力解法:
创建数组,存放所有请求
- 整型数组,存放10000个元素
- 把当前请求存入数组
- 记录最后一次存入的索引,从零开始
统计距离此次请求前3000ms之间的请求次数
从最后一次存放位置倒序遍历
class RecentCounter { array: number[]; constructor() { this.array = []; // 创建数组,存放所有请求。 因为js数组是可以动态扩容的 } ping(t: number): number { let end = 0; // 最近一次请求的开始索引,从零开始 for (let i = 0; i < 10 ** 4; i++) { if (!this.array[i]) { // 数组为空的时候没有存放任何请求 this.array[i] = t; end = i; // 记录最近一次操作的索引 break; } }; let count = 0; // 计数器 while(this.array[end] >= (t - 3000)) { // 数组元素要在要求的范围内 count++; if (--end < 0) { // 倒序遍历 防止越界 break; } } return count } }时间复杂度:O(n ^ 2)
- 存入数组,每次遍历n-1个位置:O(n - 1)
- 统计请求次数,每次遍历1 ~ n个位置:O(n)
- ping方法会调用n次:n(n - 1 + n) —> n ^ 2
空间复杂度:O(1)
- 剔除无效代码或优化空间消耗
- 创建成千上万个容量的数组比较浪费空间,能够动态扩展容器的容量
- 是否有其它更方便的数据结构
- 寻找更好的算法思维
队列
- 数据结构选择:队列
- 始终在一端插入数据,另一端删除数据
- 先进先出
- 插入和删除时间复杂度:O(1)
- 基本操作
- 入队
- 出队
最优解:
队列解法思路分析
- 使用链表实现一个队列
- 定义属性:队头、队尾、长度
- 定义方法:添加节点、移除节点、队列长度
- 定义内部类:封装每次入队的请求数据和指向下一个节点的指针
- 每次请求向队列尾部追加节点
- 循环检查头部数据是否合法,不合法则移除节点
- 返回队列长度
边界细节问题和复杂度分析
- 边界问题
- 入队和出队始终关注首尾节点指针
细节问题
- 第一次添加节点,首尾指针都是null
- 每次追加尾节点,size++,重置tail
- 每次移除头结点,size—,重置head ```typescript class RecentCounter { array: number[]; constructor() { this.array = []; }
ping(t: number): number { this.array.push(t); // 主要是借助js特性完成了队列的思想
while(this.array[0] < (t - 3000)) {
this.array.shift();};
return this.array.length; } } ``` 时间复杂度:O(n)
添加和删除元素为1
- ping方法调用n次
空间复杂度: O(1)
总结
队列的特点:
- 生产者消费者模式:CPU调度多线程;消息队列,作为缓冲区提高效率
