Hi 你们好,我是张小猪。欢迎来到『宝宝也能看懂』系列之 leetcode 周赛题解。node
这里是第 175 期的第 3 题,也是题目列表中的第 1348 题 -- 『推文计数』git
请你实现一个可以支持如下两种方法的推文计数类 TweetCounts
:github
recordTweet(string tweetName, int time)
shell
tweetName
在 time
(以 秒 为单位)时刻发布了一条推文。getTweetCountsPerFrequency(string freq, string tweetName, int startTime, int endTime)
segmentfault
startTime
(以 秒 为单位)到结束时间 endTime
(以 秒 为单位)内,每 分 minute,时 hour 或者 日 day (取决于 freq
)内指定用户 tweetName
发布的推文总数。freq
的值始终为 分 minute,时 hour 或者 日 day 之一,表示获取指定用户 tweetName
发布推文次数的时间间隔。startTime
开始,所以时间间隔为 [startTime, startTime + delta*1>, [startTime + delta*1, startTime + delta*2>, [startTime + delta*2, startTime + delta*3>, ... , [startTime + delta*i, min(startTime + delta*(i+1), endTime + 1)>
,其中 i
和 delta
(取决于 freq
)都是非负整数。示例:数组
输入: ["TweetCounts","recordTweet","recordTweet","recordTweet","getTweetCountsPerFrequency","getTweetCountsPerFrequency","recordTweet","getTweetCountsPerFrequency"] [[],["tweet3",0],["tweet3",60],["tweet3",10],["minute","tweet3",0,59],["minute","tweet3",0,60],["tweet3",120],["hour","tweet3",0,210]] 输出: [null,null,null,null,[2],[2,1],null,[4]] 解释: TweetCounts tweetCounts = new TweetCounts(); tweetCounts.recordTweet("tweet3", 0); tweetCounts.recordTweet("tweet3", 60); tweetCounts.recordTweet("tweet3", 10); // "tweet3" 发布推文的时间分别是 0, 10 和 60 。 tweetCounts.getTweetCountsPerFrequency("minute", "tweet3", 0, 59); // 返回 [2]。统计频率是每分钟(60 秒),所以只有一个有效时间间隔 [0,60> - > 2 条推文。 tweetCounts.getTweetCountsPerFrequency("minute", "tweet3", 0, 60); // 返回 [2,1]。统计频率是每分钟(60 秒),所以有两个有效时间间隔 1) [0,60> - > 2 条推文,和 2) [60,61> - > 1 条推文。 tweetCounts.recordTweet("tweet3", 120); // "tweet3" 发布推文的时间分别是 0, 10, 60 和 120 。 tweetCounts.getTweetCountsPerFrequency("hour", "tweet3", 0, 210); // 返回 [4]。统计频率是每小时(3600 秒),所以只有一个有效时间间隔 [0,211> - > 4 条推文。
提示:数据结构
recordTweet
和 getTweetCountsPerFrequency
,最多有 10000
次操做。0 <= time, startTime, endTime <= 10^9
0 <= endTime - startTime <= 10^4
MEDIUM函数
题目的描述和示例看起来都挺长的,不过主要都是为了解释清楚那个时间间隔的计算逻辑。其中须要注意的是,每个时间间隔的区间,是左闭右开的区间,例如 [startTime, startTime + delta)
。若是题目描述中看着不是很明白的话,能够结合示例中的数据和结果,相信就很容易弄明白这里面的逻辑啦。性能
不过说实话,小猪看完这道题以后是懵逼的。由于小猪没看出其中有什么玄机,彷佛按照要求直接实现就完事了。但是这也是周赛第三题了吧,怎么也该给它一点面子鸭...hmmmm...装模做样的思考了一下,算了,淦就完事了!优化
因为须要根据 tweetName
来匹配时间,因此咱们经过一个字典来进行存储。而后对于给定的 start
和 end
两个参数,咱们能够结合 freq
轻松的计算出时间间隔的数量。最后,遍历计数便可。具体流程以下:
构造函数
freq
的转换字典以及用于存储数据的字典。recordTweet
name
去字典里记录 time
便可。getTweetCountsPerFrequency
freq
获取到时间间隔长度。start
和 end
计算出时间间隔数量。name
获取时间列表并进行遍历和计数。基于这个流程,咱们能够实现相似下面的代码:
class TweetCounts { constructor() { this.freqInterval = { minute: 60, hour: 3600, day: 86400, }; this.data = new Map(); } recordTweet(name, time) { if (this.data.has(name) === false) { this.data.set(name, []); } this.data.get(name).push(time); } getTweetCountsPerFrequency(freq, name, start, end) { const interval = this.freqInterval[freq]; const ret = new Uint16Array(Math.ceil((end - start + 1) / interval)); if (this.data.has(name)) { for (const time of this.data.get(name)) { if (time > end || time < start) continue; ++ret[Math.floor((time - start + 1) / interval)]; } } return ret; } };
上面的代码中,因为咱们存储的时间是无序的,因此每次 getTweetCountsPerFrequency
方法都会把对应那个 name
的时间列表遍历一遍,须要消耗 O(n) 的代价。这里咱们能够经过二叉树把它优化到 O(logn) 的时间,不过相应的,每次增长一个 time
的时间会从 O(1) 也变成 O(logn)。
具体的方式就是咱们不用数组来存储时间,而是替换为一个二叉搜索树。流程其实仍是同样的,因此这里就直接给代码了:
const createNode = val => ({ val, left: null, right: null }); class BinarySearchTree { constructor() { this.root = null; } insert(val, cur = this.root) { const node = createNode(val); if (!this.root) { this.root = node; return; } if (val >= cur.val) { !cur.right ? (cur.right = node) : this.insert(val, cur.right); } else { !cur.left ? (cur.left = node) : this.insert(val, cur.left); } } traversal(low, high, interval, intervals, cur = this.root) { if (!cur) return; if (cur.val <= high && cur.val >= low) { ++intervals[Math.floor((cur.val - low + 1) / interval)]; } cur.val > low && this.traversal(low, high, interval, intervals, cur.left); cur.val < high && this.traversal(low, high, interval, intervals, cur.right); } }; class TweetCounts { constructor() { this.freqInterval = { minute: 60, hour: 3600, day: 86400, }; this.data = new Map(); } recordTweet(name, time) { if (this.data.has(name) === false) { this.data.set(name, new BinarySearchTree()); } this.data.get(name).insert(time); } getTweetCountsPerFrequency(freq, name, start, end) { const interval = this.freqInterval[freq]; const ret = new Uint16Array(Math.ceil((end - start + 1) / interval)); this.data.has(name) && this.data.get(name).traversal(start, end, interval, ret); return ret; } };
这段代码以 192ms 暂时 beats 100%。不过很显然,能够继续优化,由于我这里只是用了很是简单的二叉搜索树,并不能保证树的深度是 logn,能够轻松的构建出一些用例让它退化为 O(n) 性能。因此,咱们能够把它替换为一些自平衡二叉搜索树,以保证树的深度。
可是,JS 里缺乏太多内置的数据结构了,因此我这里就先不写啦,等到咱们数据结构专题那边再作相关的实现吧。哼,才不是由于小猪懒呢(确信脸
说实话这道题确实让小猪比较懵逼。写这篇文章的时候看了一眼经过率,只有 20%+。但是小猪却一直不太明白这道题有什么玄机,也不明白为何经过率这么低,更不明白这道题的点究竟在哪里。
hmmmm...必定是小猪太苯了,get 不到这个点。小猪仍是去快乐的恰饭好了,喵喵喵 >.<