力扣355
设计推特问题十分容易被考察到,通常都是换个皮如设计朋友圈。
我们需要设计一个推特类,并且实现以下的功能:
【1】postTweet(userId, tweetId): 创建一条新的推文
【2】getNewsFeed(userId): 检索最近的十条推文。每个推文都必须是由 此用户关注的人或者是 用户自己发出的。推文必须按照 时间顺序由最近的开始排序。
【3】follow(followerId, followeeId): 关注一个用户
【4】unfollow(followerId, followeeId): 取消关注一个用户
帖子节点
每个用户可以发出多条推特,并且这些帖子需要按照时间顺序进行排序。最直观的想法就是将推特设计成链表节点,并且保存每个帖子的tweetId,而且使用一个time成员记录帖子发出的时间。
为每个用户可以保存一个Tweet头结点,用户本身可以看作一个链表,每当用户发送一个帖子则创建一个链表节点尾插入链表,末尾的帖子节点就是最新发布的帖子。
class Tweet{
int id;
int time;
Tweet next;
public Tweet(int id, int time) {
this.id = id;
this.time = time;
}
}
用户
对应用户,我们有两种处理方式:
【1】为用户设计一个User类型,每个用户有一个id,一个帖子链表的头结点和关注列表
private int id;
public Set<Integer> followed;
//用户发表的推文链表头结点
public Tweet head;
还可以在user中提供一些接口如follow和unfollow,然后提供使推特类维护一个User的集合,并且在实现上的一部分调用User提供的接口。
private HashMap<Integer,User> userMap;
【2】另一种思路是不单独设计User类,设计类是为了更好的管理代码,而本题中我们的可以简单的使用id去代指一个user的信息。
并在推特类全局维护用户的相关信息(可以看作把User对象打散为变量来存储)
Map<Integer,Tweet> tweetMap;//用户id-tweet
Map<Integer,Set<Integer>> followMap;//用户id-关注列表
推特
对应推特类,我们可以使用一个全局时间作为成员,作为一个逻辑的时间,每当有一个帖子被创建则全局的逻辑时间都会递增。
int globalTime;
发帖子
(这里以第二种设计user的方案为例,即不保存User对象而保存userId Map)
发帖子是用户的行为,而且发帖子会使得全局逻辑时间自增。
这里,我们先创建一个帖子,然后put进链表,这时map.put返回值要么是null代表第一次插入,要么是一个链表节点的引用,代表就链表节点。
我们把旧链表的next指针指向新链表。使用头插法可以将插入的时间复杂度缩小到O(1),而尾插法如果不为每个user维护尾结点则必须遍历整个链表,效率不高。
因此,每个user的帖子链表,头节点代表最新的帖子。
public void postTweet(int userId, int tweetId) {
globalTime++;
Tweet tweet = new Tweet(tweetId, globalTime);
Tweet res = tweetMap.put(userId, tweet);
if(res!=null){
//新推特在前,旧推特在后
tweet.next=res;
}
}
关注和取关
//关注别人的人 follower
public void follow(int followerId, int followeeId) {
if(followeeId==followerId)return;
if(!followMap.containsKey(followerId))
followMap.put(followerId,new HashSet<>());
followMap.get(followerId).add(followeeId);
}
注意,自己不能关注和取关自己。用户可以关注多个人,因此使用一个hashSet,使用之前需要先进行初始化。
public void unfollow(int followerId, int followeeId) {
if(followeeId==followerId)return;
if(!followMap.containsKey(followerId))return;
followMap.get(followerId).remove(followeeId);
}
关注与取关其实就是两个用户直接的hashSet做添加和删除操作。
刷新十条最新帖子
每个用户的维护一个推特头结点,且为最新节点。
一个用户有一个关注列表,那么这个关注列表可以根据tweet首节点的time进行动态排序,可以基于优先队列实现。
由于还需要显示用户自己的推特,因此将用户的tweet节点也放入优先队列。
public List<Integer> getNewsFeed(int userId) {
//这是一个临时创建的优先队列,用于取出最早发出的帖子
PriorityQueue<Tweet> tweets = new PriorityQueue<>((a,b)->b.time-a.time);
//用户自己的推特,加入优先队列
Tweet own = tweetMap.get(userId);
if(own!=null)
tweets.offer(own);
//关注者的推特
if(!followMap.containsKey(userId))
followMap.put(userId,new HashSet<>());
Set<Integer> followees = followMap.get(userId);
for(Integer id:followees){
Tweet tweet = tweetMap.get(id);
if(tweet!=null){
tweets.offer(tweet);
}
}
//优先队列初始化完毕...
LinkedList<Integer> res = new LinkedList<>();
int size=10;
while (size>0&&!tweets.isEmpty()){
Tweet cur = tweets.poll();
Tweet nextTweet = cur.next;
res.add(cur.id);
if(nextTweet!=null)tweets.offer(nextTweet);
size--;
}
return res;
}
总结:
将用户tweet节点和关注列表成员的tweet加入优先队列,完成初始化工作。接着就是依次取出有序队列中最早发出的帖子,移除头结点,然后将处理过后的节点(node=node.next)再次放入优先队列(因为一个用户可能连续发出多条time早的帖子),依次拿出十条记录后返回结果列表。