355. 设计推特

355. 设计推特

355. 设计推特

设计一个简化版的推特(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)

分析

面向对象的设计,主要逻辑在User类。

测试用例有几个很特别,需要注意:

  • 某个用户可能关注/取关自己

  • 每个Api涉及到的用户,可能当前系统里还不存在,需要检查后创建

  • main.go
type Twitter struct {
    // 根据要实现的几个Api,要维护所有用户,且能根据id迅速获得用户,用map
    Users map[int]*User
    // 全局时间,每加一条博客自增一次,用于getNewsFeed中的排序
    Time  int
}

func Constructor() Twitter {
    return Twitter{Users: map[int]*User{}}
}

func (t *Twitter) PostTweet(userId int, tweetId int) {
    t.ensureUser(userId).PostTweet(tweetId, t.Time)
    t.Time++
}

func (t *Twitter) GetNewsFeed(userId int) []int {
    return t.ensureUser(userId).GetNewsFeed()
}

func (t *Twitter) Follow(followerId int, followeeId int) {
    t.ensureUser(followerId).Follow(t.ensureUser(followeeId))
}

func (t *Twitter) Unfollow(followerId int, followeeId int) {
    t.ensureUser(followerId).Unfollow(followeeId)
}

func (t *Twitter) ensureUser(userId int) *User {
    if user, ok := t.Users[userId]; ok {
        return user
    }
    user := &User{ID: userId, Freinds: map[int]*User{}}
    t.Users[userId] = user
    return user
}
  • tweetheap.go
const Max = 10

type Q struct {
    s []Article
}

func (q *Q) Len() int           { return len(q.s) }
func (q *Q) Less(i, j int) bool { return q.s[i].Time < q.s[j].Time }
func (q *Q) Swap(i, j int)      { q.s[i], q.s[j] = q.s[j], q.s[i] }
func (q *Q) Push(x interface{}) { q.s = append(q.s, x.(Article)) }
func (q *Q) Pop() interface{} {
    old := q.s
    n := len(old)
    res := old[n-1]
    q.s = old[:n-1]
    return res
}
func (q *Q) push(x Article) {
    if q.Len() < Max {
        heap.Push(q, x)
        return
    }
    if q.s[0].Time < x.Time {
        heap.Pop(q)
        heap.Push(q, x)
    }
}
func (q *Q) pop() Article { return heap.Pop(q).(Article) }
  • user.go
type User struct {
    ID int
    // 发表的博客
    Articles []Article
    // 好友,即关注的用户
    Freinds  map[int]*User
}

type Article struct {
    ID, Time int
}

func (u *User) PostTweet(id, time int) {
    u.Articles = append(u.Articles, Article{id, time})
}

func (u *User) Follow(user *User) {
    if user == nil || user.ID == u.ID {
        return
    }
    u.Freinds[user.ID] = user
}

func (u *User) Unfollow(id int) {
    delete(u.Freinds, id)
}

func (u *User) GetNewsFeed() []int {
    q := &Q{}
    // 每个人的博客最多取最近发表的10条
    getLastMax := func(articles []Article) []Article {
        from := max(0, len(articles)-Max)
        return articles[from:]
    }
    for _, v := range getLastMax(u.Articles) {
        q.push(v)
    }
    for _, f := range u.Freinds {
        if f == nil {
            continue
        }
        for _, v := range getLastMax(f.Articles) {
            q.push(v)
        }
    }
    res := make([]int, q.Len())
    for i := len(res) - 1; i >= 0; i-- {
        res[i] = q.pop().ID
    }
    return res
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

getNewsFeed 复杂度分析:

假设n为用户及其关注用户所有tweet的个数, 时间复杂度O(nlogn), 最坏情况下所有tweet都要插入堆里一次; 空间复杂度O(1), 堆里最多有10个元素。