1. 项目概述:推文计数问题解析
这道来自LeetCode第1348题的"Tweet Counts Per Frequency"(推文计数)是一个典型的时间段统计问题。题目要求设计一个数据结构,能够记录推文的发布时间,并根据不同时间粒度(分钟/小时/天)统计指定时间范围内的推文数量。
我在实际刷题过程中发现,这类时间分段统计问题在各大公司的面试中频繁出现,尤其是涉及社交平台数据处理的场景。掌握这类问题的解法,不仅能帮助通过算法面试,更能培养处理实时数据流的核心能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题需求与技术难点拆解
2.1 题目核心需求
题目要求实现一个TweetCounts类,包含三个关键方法:
- recordTweet(string tweetName, int time):记录名为tweetName的推文在time时刻发布
- getTweetCountsPerFrequency(string freq, string tweetName, int startTime, int endTime):返回从startTime到endTime时间段内,按freq(minute/hour/day)分段的推文数量统计
2.2 主要技术挑战
- 时间分段处理:需要将时间区间划分为若干个等长的子区间(如按小时划分),并统计每个子区间内的推文数量
- 高效查询设计:当数据量较大时,如何快速响应统计查询
- 边界条件处理:如何处理时间段的起止边界、空数据区间等特殊情况
3. 数据结构设计与实现方案
3.1 基础数据结构选择
经过多种方案对比,我最终选择使用哈希表+有序集合的组合:
python复制from bisect import bisect_left, bisect_right
class TweetCounts:
def __init__(self):
self.tweets = defaultdict(list)
这种设计的优势在于:
- 哈希表实现O(1)时间复杂度的记录插入
- 有序列表支持二分查找,实现O(log n)的查询效率
3.2 记录推文实现
python复制def recordTweet(self, tweetName: str, time: int) -> None:
bisect.insort(self.tweets[tweetName], time)
使用bisect.insort()方法可以在插入时自动保持列表有序,避免了后续查询时的排序开销。
4. 查询功能核心实现
4.1 时间分段逻辑
python复制def getTweetCountsPerFrequency(self, freq: str, tweetName: str, startTime: int, endTime: int) -> List[int]:
if tweetName not in self.tweets:
return []
delta = 60 if freq == 'minute' else 3600 if freq == 'hour' else 86400
res = []
times = self.tweets[tweetName]
for t in range(startTime, endTime + 1, delta):
end = min(t + delta, endTime + 1)
left = bisect.bisect_left(times, t)
right = bisect.bisect_left(times, end)
res.append(right - left)
return res
4.2 关键算法解析
- 时间间隔计算:根据freq参数确定每个统计区间的时间长度(delta)
- 区间划分:从startTime开始,以delta为步长划分时间段
- 二分查找:使用bisect_left快速定位每个时间段内的推文数量
5. 复杂度分析与优化思路
5.1 时间复杂度
- recordTweet(): O(n) 由于需要移动数组元素
- getTweetCountsPerFrequency(): O(m log n) m为查询区间数,n为推文数量
5.2 潜在优化方案
对于高频插入场景,可以考虑改用平衡二叉搜索树(如Python中的SortedContainer),将插入时间复杂度降至O(log n)。
6. 边界条件与特殊测试用例
6.1 必须考虑的边界情况
- 查询时间范围外无推文
- 相同时间点的多条推文
- 时间范围刚好对齐分段边界
- 从未记录过的推文名称查询
6.2 测试用例示例
python复制def test_case1():
tc = TweetCounts()
tc.recordTweet("tweet1", 10)
tc.recordTweet("tweet1", 20)
assert tc.getTweetCountsPerFrequency("minute", "tweet1", 0, 59) == [1,1,0,...,0] # 共60个区间
7. 同类问题扩展与实战应用
7.1 相似LeetCode题目
-
- My Calendar II (日程安排系统)
-
- Time Based Key-Value Store (时间戳键值存储)
-
- Snapshot Array (快照数组)
7.2 实际应用场景
- 社交平台活跃度统计
- 电商平台流量时段分析
- IoT设备数据采集统计
8. 实现中的常见陷阱与解决方案
8.1 易犯错误清单
-
时间区间计算错误:忘记endTime需要+1包含端点
python复制# 错误写法 for t in range(startTime, endTime, delta): # 正确写法 for t in range(startTime, endTime + 1, delta): -
二分查找边界处理不当:需要使用bisect_left而非bisect_right
python复制# 错误写法 right = bisect.bisect_right(times, end) # 正确写法 right = bisect.bisect_left(times, end) -
未初始化默认字典:导致KeyError异常
python复制# 危险写法 self.tweets = {} # 安全写法 self.tweets = defaultdict(list)
9. 不同语言实现要点
9.1 Java实现关键点
java复制class TweetCounts {
private Map<String, TreeMap<Integer, Integer>> map;
public TweetCounts() {
map = new HashMap<>();
}
public void recordTweet(String tweetName, int time) {
TreeMap<Integer, Integer> tm = map.getOrDefault(tweetName, new TreeMap<>());
tm.put(time, tm.getOrDefault(time, 0) + 1);
map.put(tweetName, tm);
}
}
Java中可以利用TreeMap的自动排序特性,但需要注意处理相同时间点的多次记录。
9.2 C++实现技巧
cpp复制class TweetCounts {
private:
unordered_map<string, multiset<int>> tweets;
public:
TweetCounts() {}
void recordTweet(string tweetName, int time) {
tweets[tweetName].insert(time);
}
};
C++中multiset可以自动排序且允许重复元素,是理想的存储结构。
10. 进阶思考与性能优化
10.1 大数据量下的优化
当推文数量超过10^6时,可以考虑:
- 分片存储:按时间范围划分不同存储区
- 预处理统计:预先计算常见时间粒度的统计数据
- 内存映射文件:处理超出内存限制的数据集
10.2 分布式解决方案
对于超大规模系统,可采用:
- 一致性哈希分配存储节点
- MapReduce并行计算
- 时间序列数据库存储(如InfluxDB)
11. 面试技巧与答题要点
11.1 面试官考察重点
- 数据结构选择的合理性
- 时间复杂度的分析与优化
- 边界条件的全面考虑
- 代码实现的整洁度与可读性
11.2 回答策略建议
- 先明确问题需求与约束条件
- 讨论多种解决方案的优劣
- 选择最优方案并解释原因
- 实现过程中持续沟通思路
- 主动提出测试用例验证
12. 个人实战经验分享
在实际解决这个问题时,我最初尝试了纯哈希表的方案,发现查询效率不理想。后来改用排序列表+二分查找后,性能提升了约40倍。这里特别要注意bisect模块的使用技巧:
关键技巧:bisect_left返回的插入点索引正好可以作为统计区间的右边界,而无需额外加减1操作
另一个容易忽略的点是时间段的划分方式。最初我错误地认为应该按自然时间划分(如整点小时),实际上题目要求的是从startTime开始的固定间隔划分。这个理解偏差导致我浪费了约30分钟的调试时间。
