[路飞]_设计推特

206 阅读4分钟

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

leetcode-355 设计推特
b站视频

题目介绍

设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近 10 条推文。

实现 Twitter 类:

  • Twitter() 初始化简易版推特对象
  • void postTweet(int userId, int tweetId) 根据给定的 tweetIduserId 创建一条新推文。每次调用此函数都会使用一个不同的 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 都互不相同
  • postTweetgetNewsFeedfollowunfollow 方法最多调用 3 * 10^4

解题思路

推特是一种社交软件,如果不认识的可以类比国内的微博等社交软件,发推文、关注、取关都与微博类似

这道题是一道偏实际应用的应用题,但是是经过简化的,在实际的业务中,我们一般是将数据保存在数据库中,其中还会使用到 redis 等缓存数据库,所以在实际的开发场景中,我们一般会使用面向对象的方式来进行设计,但是在算法题中,我们还是以效率为主。所以本文将用面向对象和面向过程的两种方法来设计推特

思路一:面向过程

本题需要获取用户及其关注的用户的推文,所以我们肯定要保存一个用户列表,并且在每个用户中维护其关注的用户列表
另外题目还要求返回推文时按时间顺序从最近到最远排序,所以我们还需要维护一个按时间排序的推文数组

解题步骤

  1. 创建一个 tweets 数组,用于按时间顺序存放推文
  2. 创建一个维护用户的哈希表结构 userMap,用于映射用户与其关注的用户列表
  3. 用户创建推文时,将用户信息和推文信息从 tweets 数组前方插入,使其是一个按时间由近到远排序的顺序
  4. 获取用户的推文信息时,先判断用户是否存在 userMap 中,如果不存在,则在 userMap 中为该用户创建一个映射,然后获取该用户的关注信息,从 tweets 数组中筛选出其关注对象的所有推文,然后取这些推文的前 10
  5. 用户关注其他用户时,先判断用户是否在 userMap 中,如果不存在同上一步操作,然后将关注对象的 id 加入到该用户的关注列表中
  6. 当用户取消关注时,判断改用户是否存在,同上一步操作,然后从该用户的关注列表中,删除取消关注用户的 id

解题代码

var Twitter = function() {
    this.tweets = []
    this.userMap = new Map()
};

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

/** 
 * @param {number} userId
 * @return {number[]}
 */
Twitter.prototype.getNewsFeed = function(userId) {
    if (!this.userMap.has(userId)) this.userMap.set(userId, new Set([userId]))
    return this.tweets.filter(v => this.userMap.get(userId).has(v[0])).map(v => v[1]).slice(0, 10)
};

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.follow = function(followerId, followeeId) {
    if (!this.userMap.has(followerId)) this.userMap.set(followerId, new Set([followerId]))
    this.userMap.set(followerId, this.userMap.get(followerId).add(followeeId))
};

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.unfollow = function(followerId, followeeId) {
    if (!this.userMap.has(followerId)) this.userMap.set(followerId, new Set([followerId]))
    const val = this.userMap.get(followerId)
    val.delete(followeeId)
    this.userMap.set(followerId, val)
};

思路二:面向对象

首先此题要维护一个 User 类和 Tweet 类,Tweet 类只用于保存 tweet 的 id 和创建时间,User 类用于保存用户信息和用户的操作。
用户保存的信息有:id、关注对象、发布的推文,用户的操作方法有:发推文、关注、取消关注
另外在 Twitter 中还需要维护一个所有用户的哈希表结构

var Twitter = function() {
    this.userMap = new Map()
};

/** 
 * @param {number} userId 
 * @param {number} tweetId
 * @return {void}
 */
Twitter.prototype.postTweet = function(userId, tweetId) {
    // 如果要获取的用户不在用户表中,需要先创建该用户
    if (!this.userMap.has(userId)) this.userMap.set(userId, new User(userId))
    const user = this.userMap.get(userId)
    user.post(tweetId)
};

/** 
 * @param {number} userId
 * @return {number[]}
 */
Twitter.prototype.getNewsFeed = function(userId) {
    // 如果用户表中不存在该用户,说明该用户没有发过推文,也没有关注对象,所以直接返回空数组
    if (!this.userMap.has(userId)) return []
    // 获取当前用户
    const user = this.userMap.get(userId)
    let tweets = []
    // 将当前用户关注对象的推文信息全部放到 tweets 中
    for (const follow of user.followed) {
        tweets = tweets.concat(this.userMap.get(follow).tweets)
    }
    // 将所有的推文按时间排序,然后返回前 10 条推文的 id
    return tweets.sort((a, b) => b.time - a.time).map(v => v.tweetId).slice(0, 10)
};

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.follow = function(followerId, followeeId) {
    if (!this.userMap.has(followerId)) this.userMap.set(followerId, new User(followerId))
    // 如果被关注对象不在用户列表中,需要先创建该用户对象,否则获取被关注对象的推文时会获取不到
    if (!this.userMap.has(followeeId)) this.userMap.set(followeeId, new User(followeeId))
    const user = this.userMap.get(followerId)
    user.follow(followeeId)
};

/** 
 * @param {number} followerId 
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.unfollow = function(followerId, followeeId) {
    if (!this.userMap.has(followerId)) this.userMap.set(followerId, new User(followerId))
    const user = this.userMap.get(followerId)
    // 取消关注不需要先判断关注是因为,Set结构在删除元素时如果找不到该元素则不做任何操作
    user.unfollow(followeeId)
};

// 用户类
class User {
    constructor(userId) {
        this.userId = userId
        // 保存关注用户的列表
        this.followed = new Set()
        // 保存当前用户发的推文
        this.tweets = []
        // 创建用户时需要先将当前用户的 id 加到关注列表中,后续才能获取到包括自己在内的推文
        this.follow(userId)
    }

    // 发布推文
    post(tweetId) {
        const tweet = new Tweet(tweetId)
        this.tweets.push(tweet)
    }

    // 关注用户
    follow(userId) {
        this.followed.add(userId)
    }

    // 取消关注用户
    unfollow(userId) {
        this.followed.delete(userId)
    }
}

// Tweet 类
class Tweet {
    // 用静态属性使得所有的实例对象共用同一个变量,保证推文时间的递增
    static num = 0

    constructor(tweetId) {
        this.tweetId = tweetId
        this.time = Tweet.num++
    }
}

以上就是此题 面向过程 和 面向对象 的两种设计方法