1348. 推文计数
Hi 大家好,我是张小猪。欢迎来到『宝宝也能看懂』系列之 leetcode 周赛题解。
这里是第 175 期的第 3 题,也是题目列表中的第 1348 题 -- 『推文计数』
题目描述
请你实现一个能够支持以下两种方法的推文计数类 TweetCounts
:
-
recordTweet(string tweetName, int time)
- 记录推文发布情况:用户
tweetName
在time
(以 秒 为单位)时刻发布了一条推文。
- 记录推文发布情况:用户
-
getTweetCountsPerFrequency(string freq, string tweetName, int startTime, int endTime)
- 返回从开始时间
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 不到这个点。小猪还是去快乐的恰饭好了,喵喵喵 >.<