ヒープでいちばん重い 2 つを打ち合わせて最後に残る重さを求める

金属の玉を、いちばん重い 2 つずつ打ち合わせて減らしていき、最後に残る重さを返します。毎回「いちばん大きい値を 2 つ取り出す」を、大きい順のヒープで速くこなす練習です。

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

問題

金属の玉を打ち合わせて小さくしていく機械があります。玉の重さの並び weights を受け取ります。機械は、玉が 2 つ以上あるあいだ、次のことを繰り返します。

  • 残っている玉のうち、いちばん重い玉といちばん重い次の玉を 1 つずつ取り出して打ち合わせます
  • 2 つの重さが同じなら、2 つとも砕けて消えます
  • 重さが違うなら、軽い方は消え、重い方は重さが「2 つの差」になって機械に戻ります

玉が 1 つ以下になったら止まります。最後に残った玉の重さを返す関数を書いてください。玉が残らなければ 0 を返します。

  • weights の長さは 1 以上 1000 以下、重さはどれも 1 以上 1000 以下の整数です
  • 毎回並べ替える書き方ではなく、ヒープを使って O(n log n) で求めてください

入力と出力の例

例 1

8 と 6 を打ち合わせて差の 2 が戻り、玉は 3 と 2 になります。3 と 2 で差の 1 が戻り、最後に 1 が残ります。

weights = [3, 8, 6]
1

例 2

5 と 5 は同じ重さなので、2 つとも消えます。玉が残りません。

weights = [5, 5]
0

例 3

玉が 1 つだけなので、打ち合わせずにそのまま残ります。

weights = [7]
7

面接での聞かれ方

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

  • 「石の重さの並びがあります。いちばん重い 2 つをぶつけ合うことを繰り返して、最後に残る重さを返してください」
  • 書き終えると「毎回並べ替えるのと比べて、どれだけ速いですか?」と聞かれます
  • Python で書くと「heapq は小さい順なのに、どうやって大きい順に使いましたか?」と、符号を返す工夫の理由を聞かれます

差が 0 のときに玉を戻すか戻さないかを、最初に口に出して確かめておくと、例 2 のような所で迷いません。

ヒント

まだ開いていません

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

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

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

スタックは後から入れた物を先に、キューは先に入れた物を先に取り出します。括弧の対応や、順番待ちの処理の形です。

main.py
def last_stone(weights: list[int]) -> int:
    """
    Goal: 何を返すか

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

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

    edge case: 空、1 つだけ、など
    """
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
print(last_stone([3, 8, 6]))

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