ヒープで短い投稿の SNS のタイムラインを設計する

投稿する、​フォローする、​外す、​タイムラインを​見る、の​ 4 つの​操作を​持つ​小さな​ SNS を​作る。​人ごとの​投稿を​新しい​順の​列として​持ち、​タイムラインは​ヒープで​何本もの​列から​新しい​順に​ 10 件だけ​取り出します。

海外で定番の問題リスト 4 本のうち 1 本に入っています (NeetCode 150)

問題

短い文を投稿できる SNS の中身を、クラス PostFeed として作ってください。人は整数の番号で表します。次の 4 つの操作を持ちます。

  • post(user, postId) は、人 user が番号 postId の投稿をしたことを覚えます。投稿の番号はどれも違います
  • follow(a, b) は、人 a が人 b をフォローします。すでにフォローしていれば何もしません
  • unfollow(a, b) は、人 a が人 b のフォローを外します。フォローしていなければ何もしません
  • getFeed(user) は、人 user 自身と、user がいまフォローしている人の投稿から、新しい順に最大 10 件の投稿の番号を返します。投稿が 1 つも無ければ空の並びです

自分自身の扱いは次のように決めます。自分の投稿はいつもタイムラインに入ります。follow(a, a) と unfollow(a, a) は何もしません。フォローを外した人の投稿は、外す前の投稿も含めてタイムラインから消えます。

入力は、呼ぶ操作の名前の並び ops と、それぞれの引数の並び args で渡します。ops の最初はクラスの名前で、args の最初は空の並びです。出力は操作ごとの戻り値の並びで、作った所と post、follow、unfollow は null です。

  • 人の番号は 1 以上 500 以下、投稿の番号は 0 以上 10000 以下です
  • 操作の回数は 30000 回以下です

入力と出力の例

例 1

1 が 2 をフォローすると、2 のフォロー前の投稿も見えます。外すと見えなくなります。

入力

ops = ["PostFeed", "post",
  "post", "getFeed", "follow",
  "post", "getFeed", "unfollow",
  "getFeed"]
args = [[], [1, 101], [2, 201],
  [1], [1, 2], [2, 202], [1],
  [1, 2], [1]]

出力

[null, null, null, [101], null,
 null, [202, 201, 101], null,
 [101]]

例 2

1 は 2 をフォローし、2 人が交互に 6 回ずつ投稿します。1 のタイムラインは新しい方から 10 件で、最初の 2 件 (11 と 21) は入りません。2 は 1 をフォローしていないので、自分の投稿だけです。

入力

ops = ["PostFeed", "follow",
  "post", "post", "post",
  "post", "post", "post",
  "post", "post", "post",
  "post", "post", "post",
  "getFeed", "getFeed"]
args = [[], [1, 2],
  [1, 11], [2, 21], [1, 12],
  [2, 22], [1, 13], [2, 23],
  [1, 14], [2, 24], [1, 15],
  [2, 25], [1, 16], [2, 26],
  [1], [2]]

出力

[null, null,
 null, null, null,
 null, null, null,
 null, null, null,
 null, null, null,
 [26, 16, 25, 15, 24, 14,
  23, 13, 22, 12],
 [26, 25, 24, 23, 22, 21]]

例 3

自分をフォローしても外しても、何も変わりません。投稿の無い人のタイムラインは空です。

入力

ops = ["PostFeed", "post",
  "follow", "getFeed",
  "unfollow", "getFeed",
  "getFeed"]
args = [[], [5, 50], [5, 5],
  [5], [5, 5], [5], [9]]

出力

[null, null, null, [50], null,
 [50], []]

面接での聞かれ方

テックリードの面接では、操作の一覧だけを渡され、データの持ち方から決めさせる形が多いです。

  • 「投稿、フォロー、フォロー解除、タイムラインの取得を持つ SNS のクラスを作ってください。タイムラインは新しい順に 10 件」
  • 「フォローしている人が 1000 人いたら、タイムラインはどう作りますか」と、全部の投稿を集めて並べ替える書き方の重さを聞かれます
  • 「自分をフォローしたら?」「自分を外したら?」と、決まっていない場合を自分で決めて説明できるかを見られます
  • 「投稿を書いた時点で、フォロワーのタイムラインに配っておく方法とどちらがよいですか」と、読むときに組む方法と書くときに配る方法の取り引きを聞かれることもあります

書き始める前に、「新しい」を何で決めるか (投稿の番号ではなく投稿した順)、自分自身の扱い、外したあとに過去の投稿も消すかを確かめ、決めたことを口に出してから書きます。

アルゴリズムのチートシートの「ハッシュマップ」に、​この​型の​要点を​表と​コードで​まとめています。

main.py
class PostFeed:
    def __init__(self):
        # ここに持つ物を書きます
        pass

    def post(self, user, pid):
        # ここに書きます
        pass

    def follow(self, a, b):
        # ここに書きます
        pass

    def unfollow(self, a, b):
        # ここに書きます
        pass

    def get_feed(self, user):
        # ここに書きます
        pass


# 「実行」を押すと、下の行が動きます
obj = PostFeed()
print(obj.post(1, 101))

できているか「例で確かめる」を押すと、問題の例を全部この関数に渡して、通ったかをここに出します