查看: 127| 回复: 0
跳转到指定楼层
上一主题 下一主题
收起左侧

[高频题] [Solution] OpenAI高频题 Social Network / Follow Graph — Parts 1-4 详解

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
This is my writeup of the Social Network / Follow Graph problem, one of the current high-frequency OpenAI coding questions. It comes in 4 progressive parts plus a design follow-up. Each part builds on the previous one, and each has exactly one key insight — get that right and the code writes itself.

Part 1: immutable snapshots

SocialNetwork holds user -> set(followees). create_snapshot() must return a view that never changes afterward. The trap: a shallow dict copy still shares the inner sets, so later follow() calls would leak into old snapshots. Deep-copy the inner sets (and freeze them as frozensets — cheap insurance against accidental mutation).
  1. import bisect
  2. import heapq
  3. from collections import defaultdict
  4. class Snapshot:
  5.     def __init__(self, following):
  6.         self._following = {u: frozenset(s) for u, s in following.items()}
  7.         followers = {u: [] for u in self._following}
  8.         for u, followees in self._following.items():
  9.             for v in followees:
  10.                 followers[v].append(u)
  11.         self._following_sorted = {u: tuple(sorted(s)) for u, s in self._following.items()}
  12.         self._followers_sorted = {u: tuple(sorted(l)) for u, l in followers.items()}
  13.     def is_following(self, follower, followee):
  14.         return followee in self._following.get(follower, ())
  15.     def get_following(self, user_id):
  16.         return list(self._following_sorted.get(user_id, ()))
  17.     def get_followers(self, user_id):
  18.         return list(self._followers_sorted.get(user_id, ()))
  19.     def recommend(self, user_id, k):
  20.         if k <= 0:
  21.             return []
  22.         direct = self._following.get(user_id, frozenset())
  23.         score = defaultdict(int)
  24.         for f in direct:
  25.             for g in self._following.get(f, ()):
  26.                 if g != user_id and g not in direct:
  27.                     score[g] += 1
  28.         return heapq.nsmallest(k, score, key=lambda g: (-score[g], g))
  29. class SocialNetwork:
  30.     def __init__(self):
  31.         self._following = {}
  32.     def add_user(self, user_id):
  33.         self._following.setdefault(user_id, set())
  34.     def follow(self, follower, followee):
  35.         if follower not in self._following or followee not in self._following:
  36.             raise KeyError("unknown user")
  37.         if follower == followee:
  38.             return                          # self-follow is a no-op
  39.         self._following[follower].add(followee)   # duplicate follow is a no-op (set)
  40.     def create_snapshot(self):
  41.         return Snapshot(self._following)
复制代码

Part 2: followers

get_followers needs the reverse direction. Don't scan every user per call — build the reverse index once at snapshot construction, plus sorted tuples for both directions (already in the code above). This is safe precisely because the snapshot is immutable; on a mutable graph it would be a staleness bug. Queries become O(answer); construction is O(n + m).

Part 3: two-hop recommendations

Candidate g is reached through a followee f of the user (user -> f -> g), excluding the user and anyone already followed. Score = number of distinct followees reaching g. Since each followee's followee-set has no duplicates, incrementing once per (f, g) pair counts distinct followees correctly. Top-k by (score desc, id asc) with a heap — no need to sort all candidates. Cost: O(sum of followees' out-degrees + C log k).

Part 4: historical queries

New class, FollowTimeline: follow/unfollow(follower, followee, t) arrive in non-decreasing t, and is_following(follower, followee, t) asks about any t. Snapshots copy the whole graph; here we store, per pair, only the timestamps where the state actually flipped. A pair starts "not following", so the state at time t is following iff an odd number of flips happened at or before t: bisect_right(flips, t) odd. Calls only ever append (thanks to the non-decreasing guarantee), so queries are O(log e).

Two subtleties: same-t follow-then-unfollow records both flips, and bisect_right counts both, so the later call wins. Redundant calls (follow while already following) record nothing.
  1. class FollowTimeline:
  2.     def __init__(self):
  3.         self._flips = defaultdict(list)
  4.         self._last_t = None
  5.     def _check_time(self, t):
  6.         if self._last_t is not None and t < self._last_t:
  7.             raise ValueError("timestamps must be non-decreasing")
  8.         self._last_t = t
  9.     def _currently_following(self, key):
  10.         return len(self._flips[key]) % 2 == 1
  11.     def follow(self, follower, followee, t):
  12.         self._check_time(t)
  13.         key = (follower, followee)
  14.         if follower != followee and not self._currently_following(key):
  15.             self._flips[key].append(t)
  16.     def unfollow(self, follower, followee, t):
  17.         self._check_time(t)
  18.         key = (follower, followee)
  19.         if self._currently_following(key):
  20.             self._flips[key].append(t)
  21.     def is_following(self, follower, followee, t):
  22.         flips = self._flips.get((follower, followee))
  23.         return bool(flips) and bisect.bisect_right(flips, t) % 2 == 1
复制代码

Follow-up (discussion): copy-on-write snapshots

If snapshots are frequent and edits are few, a snapshot can be an O(1) reference to the current dict of frozensets; follow() replaces only the touched user's frozenset (plus the top-level dict — or use a persistent hash map to make that O(log n) too). Old snapshots keep pointing at old versions. The reverse index and sorted views become lazy, cached per snapshot.

Pitfalls that actually decide this question

1. Shallow-copying the snapshot (shared inner sets).
2. Rebuilding the reverse index per query instead of once at construction.
3. Counting non-distinct followees in recommendations.
4. bisect_left instead of bisect_right in Part 4 — same-timestamp ordering breaks.
5. Recording flips for redundant calls — corrupts the parity.

Happy to discuss alternatives.

上一篇:AI 时代再刷力扣 再也没有以前写代码的快乐
下一篇:[Solution] OpenAI高频题 GPU Credits — 3 Parts 详解
您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表