点の数を辞書に数え、向かいの角から軸にそろった正方形を数える

平面に点を足していき、ある点を 1 つの角とする軸にそろった正方形が何通り作れるかを数える仕組みを作ります。点ごとの個数を辞書に持ち、向かいの角を決めれば残りの 2 つの角が決まる、と気づく練習です。

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

問題

地図の道具で、目印の点を置いていき、正方形の区画を探します。次の 2 つの操作を持つクラス DetectSquares を作ってください。

  • add(point) は、点 point ([x, y] の組) を 1 つ置きます。同じ場所に何度も置けて、置いた数だけ別の点として数えます。何も返しません
  • count(point) は、問いの点 point を 1 つの角とし、残りの 3 つの角をこれまでに置いた点から選んで作れる正方形の数を返します。正方形の辺は x 軸と y 軸にそろっていて、面積は 0 より大きいものだけを数えます。問いの点そのものは置いた点に足しません

同じ場所に置いた点が 2 つあれば、その角はどちらの点を選ぶかで 2 通りと数えます。つまり答えは、3 つの角の場所それぞれに置いた点の数の積を、作れる正方形ごとに足した値です。

メソッドの名前は、JavaScript と Python は上のまま、Go は Add、Count と頭を大文字にします。Go では newDetectSquares() で作ります。

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

  • 座標はどれも 0 以上 1000 以下の整数です
  • 操作の回数は 3000 回以下です

入力と出力の例

例 1

(1, 1)、(1, 4)、(4, 1) を置くと、(4, 4) を角とする 1 辺 3 の正方形が 1 つ作れます。(2, 2) を角とする正方形は作れません。

ops = ["DetectSquares", "add", "add", "add", "count", "count"] args = [[], [[1, 1]], [[1, 4]], [[4, 1]], [[4, 4]], [[2, 2]]]
[null, null, null, null, 1, 0]

例 2

(0, 5) に 2 つ置いたので、その角の選び方が 2 通りあり、答えは 2 です。

ops = ["DetectSquares", "add", "add", "add", "add", "count"] args = [[], [[0, 0]], [[0, 5]], [[0, 5]], [[5, 5]], [[5, 0]]]
[null, null, null, null, null, 2]

例 3

(6, 6) を角とする正方形は、左下に広がる 1 辺 2 のもの (向かいの角が (4, 4)) と、右上に広がる 1 辺 1 のもの (向かいの角が (7, 7)) の 2 つです。

ops = ["DetectSquares", "add", "add", "add", "add", "add", "add", "count"] args = [[], [[4, 4]], [[4, 6]], [[6, 4]], [[7, 7]], [[6, 7]], [[7, 6]], [[6, 6]]]
[null, null, null, null, null, null, null, 2]

面接での聞かれ方

面接では、幾何の形をした設計の問題として出されます。

  • 「点を足す操作と、ある点を角とする正方形を数える操作を持つクラスを作ってください」
  • 「同じ点が何度も足されたら?」と、重複の数え方を確かめられます
  • 書き終えたあとに「count の計算量は?」「点がとても多いときはどうしますか?」と続きます

芯は、正方形の 4 つの角のうち、問いの点と向かいの角を決めれば、残りの 2 つの角の場所も決まると気づくことです。

ヒント

まだ開いていません

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

  1. ヒント 2

  2. ヒント 3答えに近い

アルゴリズムのチートシートの「配列と文字列」に、この型の要点を表とコードでまとめています。

配列と文字列の問題は、全部の組を調べると手間が大きくなる所を、窓や左右の添字で減らす形が多いです。

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

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

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

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

class DetectSquares:
    def __init__(self):
        # ここに持つ物を書きます
        pass

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

    def count(self, point):
        # ここに書きます
        pass


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

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