ヒープで流れてくる点数の k 番目に高い値をすぐ答える仕組みを作る

設計問題 131

点数が 1 つずつ届くたびに、それまでの全部の点数のうち k 番目に高い値を返すクラスを作ります。上位の k 個だけを小さい順のヒープに持ち、根を答えにする練習です。

海外で定番の問題リスト 4 本には入っていません

問題

ゲームの大会で、上位 k 位に入るのに要る点数を、新しい点数が届くたびに画面に出します。これをクラス KthLargest として作ってください。

  • 作るときに、整数 k と、それまでの点数の並び nums を受け取ります
  • add(val) は、点数 val を 1 つ足し、それまでに届いた全部の点数 (nums を含む) を高い順に並べたとき前から k 番目に来る値を返します
  • 同じ点数もそれぞれ 1 つと数えます。[9, 9, 7] で 2 番目に高い値は 9 です
  • add を呼んだ時点で、点数は必ず k 個以上あります
  • k は 1 以上 10000 以下、nums の長さは 0 以上 10000 以下、点数はどれも -10000 以上 10000 以下です。add は 10000 回まで呼びます
  • add の 1 回は O(log k) でこなしてください

例の入力は、ops に作るクラスの名前と呼ぶ操作の名前を順に、args にそれぞれの引数を並べた形です。出力は操作ごとの戻り値の並びで、作った所は null です。

入力と出力の例

例 1

k は 2 です。4 を足すと上位は 9 と 6 で、2 番目は 6 です。7 を足すと 9 と 7、2 を足しても変わらず、12 を足すと 12 と 9 になります。

ops = ["KthLargest", "add", "add", "add", "add"] args = [[2, [6, 1, 9]], [4], [7], [2], [12]]
[null, 6, 7, 7, 9]

例 2

k は 1 で、はじめの点数はありません。1 番目に高い値は、それまでの最高点です。

ops = ["KthLargest", "add", "add", "add"] args = [[1, []], [-3], [-5], [0]]
[null, -3, -3, 0]

例 3

同じ点数もそれぞれ数えます。5 が 3 つそろうと、3 番目は 5 です。6 を足しても、3 番目は 5 のままです。

ops = ["KthLargest", "add", "add", "add"] args = [[3, [5, 5]], [5], [1], [6]]
[null, 5, 5, 5]

面接での聞かれ方

面接では、短く次のように渡されることが多いです。

  • 「数が流れてくるたびに、k 番目に大きい値を返すクラスを作ってください」
  • 書いたあとに「add の 1 回はどれだけかかりますか? 全部を持つ必要はありますか?」と聞かれます
  • 「k 番目に小さい値だったら、ヒープの向きはどうしますか?」と裏返して聞かれることもあります

同じ値を別々に数えるか、add の前に k 個そろっているかを最初に確かめておくと、空のヒープを見る失敗を防げます。

ヒント

まだ開いていません

付箋は 4 枚。下の段ほど答えに近づきます

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

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

ハッシュは、値から番地を計算して、探す手間をほぼ 1 回にする仕組みです。重複や回数の問題は、この形で解けます。

main.py
"""
Goal: 何を返すか

idea1: まず思いつく案
-> runtime: O(?)  space: O(?)

idea2: もっと速い案
-> runtime: O(?)  space: O(?)

edge case: 空、1 つだけ、など
"""

class KthLargest:
    def __init__(self, k, nums):
        # ここに持つ物を書きます
        pass

    def add(self, val):
        # ここに書きます
        pass


# 「実行」を押すと、下の行が動きます
obj = KthLargest(2, [6, 1, 9])
print(obj.add(4))

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