在问 · 算法题解

355. 设计推特

中等设计哈希表 + 收集候选后排序力扣原题 ↗

题目描述

设计一个简化的推特类 Twitter,支持发推、关注、取消关注,以及查看动态。

Twitter():创建空的系统。postTweet(userId, tweetId):用户 userId 发出一条编号为 tweetId 的推文,无返回值。每次调用的 tweetId 都不同。

getNewsFeed(userId):返回该用户动态里最新的至多 10 条推文编号。动态只包含用户自己发的推文和他当前关注的人发的推文。结果按发布时间从新到旧排列。

follow(followerId, followeeId):followerId 开始关注 followeeId。unfollow(followerId, followeeId):followerId 不再关注 followeeId。这两个方法都无返回值。

示例按操作序列给出:第一行是方法名,第二行是参数,输出中无返回值的调用记为 null。

示例 1
输入:操作 = ["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]]

解释:用户 1 关注用户 2 后能看到推文 6 和 5,6 更新所以在前;取消关注后只剩自己的推文 5。

数据范围

  • 1 <= userId, followerId, followeeId <= 500
  • 0 <= tweetId <= 10^4
  • 所有推文编号互不相同。
  • 四个方法合计最多调用 3 * 10^4 次。
  • 用户不会关注自己。

解题思路:哈希表 + 收集候选后排序

全局时间戳 time 每发一条推文自增;每个用户保存 (time, tweetId) 列表和关注集合。拉 feed 时,把自己和每个关注者的最新 10 条放进候选,按时间降序取前 10 条。也可用堆做 k 路归并进一步优化。

为什么正确

每个用户的推文列表按时间戳升序,末尾最新;关注集合里没有自己。

复杂度分析

post / follow / unfollow O(1);feed O(F·10·log(F·10)),F 为关注人数。

易错点

关注自己时要忽略,否则自己的推文可能被加入两次;排序依据是时间戳而非 tweetId。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Twitter:
    def __init__(self):
        self.time = 0
        self.tweets = defaultdict(list)
        self.follows = defaultdict(set)

    def postTweet(self, userId: int, tweetId: int) -> None:
        self.time += 1
        self.tweets[userId].append((self.time, tweetId))

    def getNewsFeed(self, userId: int) -> List[int]:
        cand = []
        for u in self.follows[userId] | {userId}:
            cand.extend(self.tweets[u][-10:])
        cand.sort(reverse=True)
        return [tid for _, tid in cand[:10]]

    def follow(self, followerId: int, followeeId: int) -> None:
        if followerId != followeeId:
            self.follows[followerId].add(followeeId)

    def unfollow(self, followerId: int, followeeId: int) -> None:
        self.follows[followerId].discard(followeeId)

载入编辑器,自己跑一遍 →

其他解法

主解法把每人最近 10 条全部放在一起排序,写法简单;多路归并利用各列表的有序性,只取出真正需要的 10 条,不产生临时候选数组。

解法 2:多路归并 时间 getNewsFeed O(10·F),其余 O(1),空间 O(T + E)

每个用户的推文列表按发布时间递增,天然有序。取关注列表和自己共 F 个用户,每人一个指针指向自己最新的推文。每轮在所有指针中选出时间最大的那条加入结果,并把该用户的指针前移一位。重复最多 10 轮。

不需要复制和排序候选推文,直接利用每个列表已有序的性质,是「合并 K 个有序链表」的应用。关注人数很多时,可以把每轮的线性选最大换成大根堆,降到 O(F + 10 log F)。

class Twitter:
    def __init__(self):
        self.time = 0
        self.tweets = collections.defaultdict(list)
        self.follows = collections.defaultdict(set)

    def postTweet(self, userId: int, tweetId: int) -> None:
        self.time += 1
        self.tweets[userId].append((self.time, tweetId))

    def getNewsFeed(self, userId: int) -> List[int]:
        lists = [self.tweets[u] for u in self.follows[userId] | {userId}]
        ptr = [len(t) - 1 for t in lists]
        res = []
        while len(res) < 10:
            best = -1
            for i, t in enumerate(lists):
                if ptr[i] >= 0 and (best < 0 or t[ptr[i]][0] > lists[best][ptr[best]][0]):
                    best = i
            if best < 0:
                break
            res.append(lists[best][ptr[best]][1])
            ptr[best] -= 1
        return res

    def follow(self, followerId: int, followeeId: int) -> None:
        if followerId != followeeId:
            self.follows[followerId].add(followeeId)

    def unfollow(self, followerId: int, followeeId: int) -> None:
        self.follows[followerId].discard(followeeId)

设计 专题的其他题目

查看 设计 全部题目 →