算法:设计推特

549 阅读5分钟

设计推特

这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

正文

设计推特

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

解析

这是一道非常有意思的题,实际上就是和我们平时看到的微博是一个道理,最关键点是如何设计刷出的数据是你自己和你关注的人的数据,并且按照时间顺序排序

假设我们在正常开发中,我们大可不必使用复杂算法去设计,取而代之的是利用数据库存储以及SQL语句来实现,那么如果我们要用算法和缓存来实现的话,就可以参照数据库的实现方式,这题也就不那么难了。

设计数据库的实现

试想一下,如果我们利用数据库去设计表,应该怎么去实现。首先会有一个推文表:

推文表:

tweetId12345
userId12345

tweedId: 推文 id

userId: 发布者 id

这里用户表可以省略,因为我们不需要对用户进行注册和其他操作。接下来是用户的关注关系表:

followerId12345
followeeId12345

followerId: 关注者id

followeeId: 被关注者id

两张表足矣。当用户发送推文的时候,就在推文表中插入数据,当用户关注时就在关注关系表里面插入数据,当用户取关时,就在关注关系表中删除对应的数据。当用户获取推文的时候,就两张表关联查询!

实际上如果使用数据库+SQL是非常好实现的,那么可以类推到算法设计中来。

实现构造函数 Twitter

var Twitter = function() {
    this.dataQuene = [] // 推文表
    this.followTable = [] // 关注关系表
};

构造函数中我们构造SQL中的两个表。

实现发布twitter postTweet

/** 
 * @param {number} userId 
 * @param {number} tweetId
 * @return {void}
 */
Twitter.prototype.postTweet = function(userId, tweetId) {
    this.dataQuene.unshift({
        tweetId,
        userId
    })
    
};

类比数据库操作,在推文表里面插入 tweeIduserId 数据。这里为什么要用 unshift 呢,因为我们把数据插入在数组的最前面,那么我们取得时候就可以按照后发布的顺序去取了。最后发布的一条推特,应该在数组的第一个被取出来,就是这个道理。

实现用户关注 follow:

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.follow = function(followerId, followeeId) {
    if (this.followTable.some(item => item.followerId === followerId && item.followeeId === followeeId)) {
        // 已关注
        return
    } else {
        // 去关注
        this.followTable.push({
            followeeId,
            followerId
        })
    }
};

在关注关系表中,我们要先对关注者进行查重,如果已经关注了,那么不做任何操作。如果没有关注,那么就在关注表里面添加数据,数据字段就是 关注者id 和 被关注者 id。

实现用户取关unfollow:

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.unfollow = function(followerId, followeeId) {
    let removeIndex = -1
    for (let index = 0 ; index < this.followTable.length ; index++) {
        const obj = this.followTable[index]
        if (obj.followeeId === followeeId && obj.followerId === followerId) {
            removeIndex = index
            break
        }
    }
    this.followTable.splice(removeIndex, 1)
};

同理,先在关注关系表中找到对应删除的数据,然后在数组中移除。

重点!实现获取推文getNewsFeed:

/** 
 * @param {number} userId
 * @return {number[]}
 */
Twitter.prototype.getNewsFeed = function(userId) {
    let res = [] // 结果数组
    const followList = this.followTable.filter(item => item.followerId === userId) // 获取当前 userId 关注的所有人数组
    for(let index = 0 ; index < this.dataQuene.length; index++) {
        const data = this.dataQuene[index] // 遍历推文
        if (data.userId === userId || followList.some(item => data.userId === item.followeeId)) { // 推文的发布者 || 这个推文的发布者是 userId 的关注者
            res.push(data.tweetId) // 结果保存推文Id
            if (res.length === 10) {
                return res // 长度为10的时候就不再获取
            }
        }
    }
    return res
};

关键的设计难点在于获取自己发布的推文和userId关注的推文,利用关注关系表获取到当前userId所有的关注者,然后判断判断推文的发布者userId 是否存在在关注者里面就行了。

完整代码:

var Twitter = function() {
    this.dataQuene = []
    this.followTable = []
};

/** 
 * @param {number} userId 
 * @param {number} tweetId
 * @return {void}
 */
Twitter.prototype.postTweet = function(userId, tweetId) {
    this.dataQuene.unshift({
        tweetId,
        userId
    })
    
};

/** 
 * @param {number} userId
 * @return {number[]}
 */
Twitter.prototype.getNewsFeed = function(userId) {
    let res = []
    const followList = this.followTable.filter(item => item.followerId === userId)
    for(let index = 0 ; index < this.dataQuene.length; index++) {
        const data = this.dataQuene[index]
        if (data.userId === userId || followList.some(item => data.userId === item.followeeId)) {
            res.push(data.tweetId)
            if (res.length === 10) {
                return res
            }
        }
    }
    return res
};

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.follow = function(followerId, followeeId) {
    if (this.followTable.some(item => item.followerId === followerId && item.followeeId === followeeId)) {
        // 已关注
        return
    } else {
        // 去关注
        this.followTable.push({
            followeeId,
            followerId
        })
    }
};

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.unfollow = function(followerId, followeeId) {
    let removeIndex = -1
    for (let index = 0 ; index < this.followTable.length ; index++) {
        const obj = this.followTable[index]
        if (obj.followeeId === followeeId && obj.followerId === followerId) {
            removeIndex = index
            break
        }
    }
    this.followTable.splice(removeIndex, 1)
};

/**
 * Your Twitter object will be instantiated and called as such:
 * var obj = new Twitter()
 * obj.postTweet(userId,tweetId)
 * var param_2 = obj.getNewsFeed(userId)
 * obj.follow(followerId,followeeId)
 * obj.unfollow(followerId,followeeId)
 */

本题利用了类比数据库设计的算法去解决,是不难的,如果对数据库设计这一块一点都不懂得话,可能就比较难解决或者绕更弯的路。最后,自信一点,提交!

image.png

没有什么比一次提交通过更快乐的事了!