设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近 10
条推文。
实现 Twitter
类:
Twitter()
初始化简易版推特对象
void postTweet(int userId, int tweetId)
根据给定的 tweetId
和 userId
创建一条新推文。每次调用此函数都会使用一个不同的 tweetId
。
List<Integer> getNewsFeed(int userId)
检索当前用户新闻推送中最近 10
条推文的 ID 。新闻推送中的每一项都必须是由用户关注的人或者是用户自己发布的推文。推文必须 按照时间顺序由最近到最远排序 。
void follow(int followerId, int followeeId)
ID 为 followerId
的用户开始关注 ID 为 followeeId
的用户。
void unfollow(int followerId, int followeeId)
ID 为 followerId
的用户不再关注 ID 为 followeeId
的用户。
示例:
**输入**
["Twitter", "postTweet", "getNewsFeed", "follow", "postTweet", "getNewsFeed", "unfollow", "getNewsFeed"]
[[], [1, 5], [1], [1, 2], [2, 6], [1], [1, 2], [1]]
**输出**
[null, null, [5], null, null, [6, 5], null, [5]]
**解释**
Twitter twitter = new Twitter();
twitter.postTweet(1, 5); // 用户 1 发送了一条新推文 (用户 id = 1, 推文 id = 5)
twitter.getNewsFeed(1); // 用户 1 的获取推文应当返回一个列表,其中包含一个 id 为 5 的推文
twitter.follow(1, 2); // 用户 1 关注了用户 2
twitter.postTweet(2, 6); // 用户 2 发送了一个新推文 (推文 id = 6)
twitter.getNewsFeed(1); // 用户 1 的获取推文应当返回一个列表,其中包含两个推文,id 分别为 -> [6, 5] 。推文 id 6 应当在推文 id 5 之前,因为它是在 5 之后发送的
twitter.unfollow(1, 2); // 用户 1 取消关注了用户 2
twitter.getNewsFeed(1); // 用户 1 获取推文应当返回一个列表,其中包含一个 id 为 5 的推文。因为用户 1 已经不再关注用户 2
提示:
1 <= userId, followerId, followeeId <= 500
0 <= tweetId <= 104
所有推特的 ID 都互不相同
postTweet
、getNewsFeed
、follow
和 unfollow
方法最多调用 3 * 104
次
📺 视频题解
📖 文字题解 方法一:哈希表 + 链表 思路和算法
根据题意我们知道,对于每个推特用户,我们需要存储他关注的用户 Id
,以及自己发的推文 Id
的集合,为了使每个操作的复杂度尽可能的低,我们需要根据操作来决定存储这些信息的数据结构。注意,由于题目中没有说明用户的 Id
是否连续,所以我们需要用一个以用户 Id
为索引的哈希表来存储用户的信息。
对于操作 3
和操作 4
,我们只需要用一个哈希表存储,即可实现插入和删除的时间复杂度都为 $O(1)$。
对于操作 1
和操作 2
,由于操作 2
要知道此用户关注的人和用户自己发出的最近十条推文,因此我们可以考虑对每个用户用链表存储发送的推文。每次创建推文的时候我们在链表头插入,这样能保证链表里存储的推文的时间是从最近到最久的。那么对于操作 2
,问题其实就等价于有若干个有序的链表,我们需要找到它们合起来最近的十条推文。由于链表里存储的数据都是有序的,所以我们将这些链表进行线性归并即可得到最近的十条推文。这个操作与 23. 合并K个排序链表 基本等同。
如果我们直接照搬「合并K个排序链表」的解法来进行合并,那么无疑会造成空间的部分浪费,因为这个题目不要求你展示用户的所有推文,所以我们只要动态维护用户的链表,存储最近的 recentMax
个推文 Id
即可(题目中的 recentMax
为 10
)。那么对于操作 1
,当发现链表的节点数等于 recentMax
时,我们按题意删除链表末尾的元素,再插入最新的推文 Id
。对于操作 2
,在两个链表进行线性归并的时候,只要已合并的数量等于 recentMax
,代表已经找到这两个链表合起来后最近的 recentMax
条推文,直接结束合并即可。
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 class Twitter { struct Node { unordered_set<int > followee; list<int > tweet; }; int recentMax, time; unordered_map<int , int > tweetTime; unordered_map<int , Node> user; public : Twitter () { time = 0 ; recentMax = 10 ; user.clear (); } void init (int userId) { user[userId].followee.clear (); user[userId].tweet.clear (); } void postTweet (int userId, int tweetId) { if (user.find (userId) == user.end ()) { init (userId); } if (user[userId].tweet.size () == recentMax) { user[userId].tweet.pop_back (); } user[userId].tweet.push_front (tweetId); tweetTime[tweetId] = ++time; } vector<int > getNewsFeed (int userId) { vector<int > ans; ans.clear (); for (list<int >::iterator it = user[userId].tweet.begin (); it != user[userId].tweet.end (); ++it) { ans.emplace_back (*it); } for (int followeeId: user[userId].followee) { if (followeeId == userId) continue ; vector<int > res; res.clear (); list<int >::iterator it = user[followeeId].tweet.begin (); int i = 0 ; while (i < (int )ans.size () && it != user[followeeId].tweet.end ()) { if (tweetTime[(*it)] > tweetTime[ans[i]]) { res.emplace_back (*it); ++it; } else { res.emplace_back (ans[i]); ++i; } if ((int )res.size () == recentMax) break ; } for (; i < (int )ans.size () && (int )res.size () < recentMax; ++i) res.emplace_back (ans[i]); for (; it != user[followeeId].tweet.end () && (int )res.size () < recentMax; ++it) res.emplace_back (*it); ans.assign (res.begin (),res.end ()); } return ans; } void follow (int followerId, int followeeId) { if (user.find (followerId) == user.end ()) { init (followerId); } if (user.find (followeeId) == user.end ()) { init (followeeId); } user[followerId].followee.insert (followeeId); } void unfollow (int followerId, int followeeId) { user[followerId].followee.erase (followeeId); } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 class Twitter { private class Node { Set<Integer> followee; LinkedList<Integer> tweet; Node() { followee = new HashSet <Integer>(); tweet = new LinkedList <Integer>(); } } private int recentMax, time; private Map<Integer, Integer> tweetTime; private Map<Integer, Node> user; public Twitter () { time = 0 ; recentMax = 10 ; tweetTime = new HashMap <Integer, Integer>(); user = new HashMap <Integer, Node>(); } public void init (int userId) { user.put(userId, new Node ()); } public void postTweet (int userId, int tweetId) { if (!user.containsKey(userId)) { init(userId); } if (user.get(userId).tweet.size() == recentMax) { user.get(userId).tweet.remove(recentMax - 1 ); } user.get(userId).tweet.addFirst(tweetId); tweetTime.put(tweetId, ++time); } public List<Integer> getNewsFeed (int userId) { LinkedList<Integer> ans = new LinkedList <Integer>(); for (int it : user.getOrDefault(userId, new Node ()).tweet) { ans.addLast(it); } for (int followeeId : user.getOrDefault(userId, new Node ()).followee) { if (followeeId == userId) { continue ; } LinkedList<Integer> res = new LinkedList <Integer>(); int tweetSize = user.get(followeeId).tweet.size(); Iterator<Integer> it = user.get(followeeId).tweet.iterator(); int i = 0 ; int j = 0 ; int curr = -1 ; if (j < tweetSize) { curr = it.next(); while (i < ans.size() && j < tweetSize) { if (tweetTime.get(curr) > tweetTime.get(ans.get(i))) { res.addLast(curr); ++j; if (it.hasNext()) { curr = it.next(); } } else { res.addLast(ans.get(i)); ++i; } if (res.size() == recentMax) { break ; } } } for (; i < ans.size() && res.size() < recentMax; ++i) { res.addLast(ans.get(i)); } if (j < tweetSize && res.size() < recentMax) { res.addLast(curr); for (; it.hasNext() && res.size() < recentMax;) { res.addLast(it.next()); } } ans = new LinkedList <Integer>(res); } return ans; } public void follow (int followerId, int followeeId) { if (!user.containsKey(followerId)) { init(followerId); } if (!user.containsKey(followeeId)) { init(followeeId); } user.get(followerId).followee.add(followeeId); } public void unfollow (int followerId, int followeeId) { user.getOrDefault(followerId, new Node ()).followee.remove(followeeId); } }
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Twitter : class Node : def __init__ (self ): self.followee = set () self.tweet = list () def __init__ (self ): self.time = 0 self.recentMax = 10 self.tweetTime = dict () self.user = dict () def postTweet (self, userId: int , tweetId: int ) -> None : if userId not in self.user: self.user[userId] = Twitter.Node() self.user[userId].tweet.append(tweetId) self.time += 1 self.tweetTime[tweetId] = self.time def getNewsFeed (self, userId: int ) -> List [int ]: if userId not in self.user: return list () ans = self.user[userId].tweet[-10 :][::-1 ] for followeeId in self.user[userId].followee: if followeeId in self.user: opt = self.user[followeeId].tweet[-10 :][::-1 ] i, j, combined = 0 , 0 , list () while i < len (ans) and j < len (opt): if self.tweetTime[ans[i]] > self.tweetTime[opt[j]]: combined.append(ans[i]) i += 1 else : combined.append(opt[j]) j += 1 combined.extend(ans[i:]) combined.extend(opt[j:]) ans = combined[:10 ] return ans def follow (self, followerId: int , followeeId: int ) -> None : if followerId != followeeId: if followerId not in self.user: self.user[followerId] = Twitter.Node() self.user[followerId].followee.add(followeeId) def unfollow (self, followerId: int , followeeId: int ) -> None : if followerId != followeeId: if followerId in self.user: self.user[followerId].followee.discard(followeeId)
复杂度分析