1. 问题描述:
设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近十条推文。你的设计需要支持以下的几个功能:
- postTweet(userId, tweetId): 创建一条新的推文
- getNewsFeed(userId): 检索最近的十条推文。每个推文都必须是由此用户关注的人或者是用户自己发出的。推文必须按照时间顺序由最近的开始排序。
- follow(followerId, followeeId): 关注一个用户
- unfollow(followerId, followeeId): 取消关注一个用户
示例:
Twitter twitter = new Twitter();
// 用户1发送了一条新推文 (用户id = 1, 推文id = 5).
twitter.postTweet(1, 5);
// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文.
twitter.getNewsFeed(1);
// 用户1关注了用户2.
twitter.follow(1, 2);
// 用户2发送了一个新推文 (推文id = 6).
twitter.postTweet(2, 6);
// 用户1的获取推文应当返回一个列表,其中包含两个推文,id分别为 -> [6, 5].
// 推文id6应当在推文id5之前,因为它是在5之后发送的.
twitter.getNewsFeed(1);
// 用户1取消关注了用户2.
twitter.unfollow(1, 2);
// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文.
// 因为用户1已经不再关注用户2.
twitter.getNewsFeed(1);
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/design-twitter
2. 思路分析:
分析题目可以知道我们需要使用某些数据结构来存储题目中涉及到的所有有用的信息,由题目可知需要存储的信息有以下几个:
- 每个用户发的推文信息,主要是推文id
- 每个用户的关注者信息,主要是关注者id,也即用户id
对于第一个信息我们可以使用哈希表来存储,因为使用的是python语言所以使用字典tweets来存储,字典的键表示用户id,值表示用户id发布的所有推文,值为List类型,因为后面在检索推文的时候是按照时间进行检索的,所以在发布新的推文的时候我们可以标记一下每条推文发布的时间,也即对应的时间戳,我们可以声明一个int类型的全局变量ts表示时间戳,一开始的时候为1,每发布一条计数加1即可。我们在发布新的推文的时候在对应的用户id发布推文的字典中插入元组类型的元素,元组的第一个值表示推文发布的时间,第二个为推文id,这样方便后面检索发布时间最近的十条推文。
对于第二个信息我们也是可以使用字典follows来存储,键为用户id,值为set类型的关注者id。调用postTweet方法的时候可以直接往tweets字典中插入推文信息即可,调用follow方法也是类似的也是直接往字典中添加对应的关注者id即可,调用unfollow方法的时候使用remove方法删除对应的用户id即可。最麻烦的是检索最近发布的十条推文,我们可以遍历一下当前用户id对应的所有关注者id,遍历关注者id发布的所有推文信息,因为是需要发布时间最近的所以我们考虑使用大根堆来维护关注者发布的推文信息(所以之前需要使用一个时间戳变量来维护推文信息),将所有关注者的推文信息插入到大根堆中,因为python只有小根堆(heapq模块),我们在插入推文信息到小根堆的时候可以对推文发布的时间取一个负号,这样相当于维护的是一个大根堆,负得越多那么会排列在前面,时间就越靠前,所以在堆前面的10条信息就是最近发布的10条推文信息。
我们其实是需要理清楚题目中需要存储什么信息,结合具体的语言使用能够合适的数据结构存储起来即可。
3. 代码如下:
import collections
from typing import List
import heapq
class Twitter:
def __init__(self):
# 时间戳用来记录推文发送的时间
ts = 0
# 用户发送的推特列表, 键表示用户id, 值表示推特的用户发送的推文信息
tweets = collections.defaultdict(list)
# 关注列表
follows = collections.defaultdict(set)
self.ts = ts
self.tweets = tweets
# 关注者列表
self.follows = follows
def postTweet(self, userId: int, tweetId: int) -> None:
ts = self.ts
tweets = self.tweets
# 列表的元素为元组类型: 当前用户发布的推特时间和tweetId
tweets[userId].append((ts, tweetId))
# 更新时间戳
self.ts += 1
# 比较麻烦的是这个方法
def getNewsFeed(self, userId: int) -> List[int]:
# 每个用户的发布的推文信息存储到一个大根堆中, 最后找出堆中前十条推文信息就是需要求解的答案
# 列表作为堆
h = list()
follows = self.follows
# 因为需要检索自己的推文所以需要关注者需要插入自己的用户id
follows[userId].add(userId)
follows = self.follows
tweets = self.tweets
# 遍历所有的关注者id
for f_id in follows[userId]:
u_tweets = tweets[f_id]
for u_t in u_tweets:
# 将推文信息维护到堆中
heapq.heappush(h, (-u_t[0], u_t[1]))
i = 0
res = list()
while h and i < 10:
tweet = heapq.heappop(h)
res.append(tweet[1])
i += 1
return res
# 更新关注列表
def follow(self, followerId: int, followeeId: int) -> None:
follows = self.follows
follows[followerId].add(followeeId)
# 移除掉用户对应关注集合中的关注者id即可
def unfollow(self, followerId: int, followeeId: int) -> None:
follows = self.follows
# 注意需要先判断一下是否有这个id, 有才删除
if followeeId in follows[followerId]:
follows[followerId].remove(followeeId)