1. 题目背景与需求解析
1348题"Tweet Counts Per Frequency"是LeetCode平台上的一道中等难度设计题,主要考察数据结构的选择和时间窗口处理能力。题目要求实现一个记录推文发送时间的系统,并能够按照不同时间粒度统计推文数量。
1.1 问题场景还原
假设我们正在开发一个社交媒体分析工具,需要跟踪特定用户在三个时间粒度下的推文频率:
- 每分钟(minute)
- 每小时(hour)
- 每天(day)
系统需要支持两种操作:
- 记录推文:存储推文发送时间戳
- 查询统计:给定时间范围[start, end),返回按指定频率划分的各时间段的推文数量
1.2 输入输出示例分析
以题目给出的示例为例:
python复制tweetCounts = TweetCounts()
tweetCounts.recordTweet("tweet1", 0) # 时间戳0
tweetCounts.recordTweet("tweet1", 60) # 时间戳60
tweetCounts.getTweetCountsPerFrequency("minute", "tweet1", 0, 60)
# 返回[2]:表示0-60分钟内共有2条
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据结构设计与算法思路
2.1 核心数据结构选型
面对时间序列数据的存储和查询,常见的选择有:
-
有序数组/列表:
- 插入时保持有序性(O(n)时间)
- 查询时可以使用二分查找(O(log n))
-
平衡二叉搜索树:
- 插入和查询都是O(log n)
- 但实现较复杂
-
哈希表+排序:
- 插入O(1),查询时临时排序
- 适合插入多查询少的场景
经过比较,我选择有序字典结构(C++中的map,Python中的bisect维护的列表),因为:
- 推文时间戳天然有序
- 查询时需要频繁进行范围搜索
- 各语言都有成熟的二分查找库支持
2.2 时间窗口计算算法
统计逻辑的核心在于正确划分时间块。以分钟频率为例:
- 计算总时间跨度:delta = end - start
- 计算块数量:chunks = ceil(delta / 60)
- 对每个块[i_start, i_end),统计时间戳在该区间内的推文数
需要注意边界条件:
- 最后一个块可能不完整
- 时间戳等于start的要包含,等于end的不包含
3. 代码实现与优化
3.1 基础版本实现(Python)
python复制import bisect
from math import ceil
class TweetCounts:
def __init__(self):
self.tweets = defaultdict(list)
def recordTweet(self, tweetName: str, time: int) -> None:
bisect.insort(self.tweets[tweetName], time)
def getTweetCountsPerFrequency(self, freq: str, tweetName: str, start: int, end: int) -> List[int]:
if tweetName not in self.tweets:
return []
delta = 60 if freq == 'minute' else 3600 if freq == 'hour' else 86400
chunks = ceil((end - start) / delta)
res = [0] * chunks
lst = self.tweets[tweetName]
for i in range(chunks):
chunk_start = start + i * delta
chunk_end = min(chunk_start + delta, end)
left = bisect.bisect_left(lst, chunk_start)
right = bisect.bisect_left(lst, chunk_end)
res[i] = right - left
return res
3.2 关键操作时间复杂度分析
-
recordTweet操作:
- bisect.insort是O(n)时间复杂度
- 可以使用更高效的数据结构优化
-
getTweetCountsPerFrequency操作:
- 二分查找是O(log n)
- 需要执行chunks次查找
- 总体是O(k log n),k是块数量
3.3 优化方向探讨
对于大规模数据可以考虑:
- 改用平衡树结构(如C++的multiset)
- 预先计算不同粒度的统计结果
- 使用跳表等更高效的区间查询结构
4. 边界条件与测试用例
4.1 必须考虑的边界情况
-
时间戳等于边界值:
- start和end相等
- 推文时间戳正好等于start或end
-
频率划分不整除:
- 如统计5分钟段,但时间范围不是5的倍数
-
极端大数据量:
- 高频插入时的性能
- 大时间范围查询时的内存使用
4.2 测试用例设计示例
python复制def test_boundary():
tc = TweetCounts()
tc.recordTweet("t1", 0)
tc.recordTweet("t1", 60)
assert tc.getTweetCountsPerFrequency("minute", "t1", 0, 60) == [2]
assert tc.getTweetCountsPerFrequency("minute", "t1", 0, 1) == [1]
assert tc.getTweetCountsPerFrequency("minute", "t1", 59, 60) == [1]
assert tc.getTweetCountsPerFrequency("minute", "t1", 60, 120) == [1]
5. 同类题型扩展与比较
5.1 LeetCode类似题目对比
-
731. My Calendar II:
- 同样涉及时间区间管理
- 但需要处理重叠区间计数
-
1157. Online Majority Element In Subarray:
- 统计查询类问题
- 需要更复杂的数据结构
-
1606. Find Servers That Handled Most Number of Requests:
- 时间窗口统计
- 服务器负载均衡场景
5.2 实际工程应用场景
这类时间序列统计在以下场景很常见:
- 网站访问量统计(每分钟PV/UV)
- 系统监控指标收集(CPU使用率)
- 金融交易频率分析
在工程实现中,通常会使用专门的时间序列数据库(如InfluxDB)或流处理框架(如Flink)来处理这类需求。这道题目可以看作是对这些系统底层原理的简化模拟。
6. 解题心得与面试技巧
6.1 解题过程中的关键点
-
数据结构选择:
- 面试时要明确说明选择理由
- 比较不同方案的trade-off
-
时间窗口处理:
- 注意左闭右开区间
- 处理不整除的情况
-
边界条件:
- 空输入
- 极值情况
6.2 面试中可能的问题
面试官可能会追问:
- 如果推文量非常大(如百万级),如何优化?
- 如果需要支持更多统计粒度(如每周),如何扩展?
- 如何设计分布式版本?
建议准备思路:
- 分片存储
- 预聚合统计
- 近似算法(如HyperLogLog)
6.3 个人踩坑记录
在实际实现中,我最初犯的错误包括:
- 没有使用bisect.insort,导致列表无序
- 错误处理了时间窗口边界(把end包含在内)
- 没有考虑频率不整除的情况
经过多次测试才修正这些问题。这也提醒我们,处理时间相关问题时,边界条件测试特别重要。
