点の 数を 辞書に 数え、 向かいの 角から 軸に そろった 正方形を数える
平面に
海外で定番の問題リスト 4 本には入っていません
問題
地図のDetectSquares を
add(point)は、点 point([x, y]の組) を 1 つ 置きます。 同じ 場所に 何度も 置けて、 置いた 数だけ別の 点として 数えます。 何も 返しません count(point)は、問いの 点 pointを 1 つの角とし、 残りの 3 つの 角を これまでに 置いた 点から 選んで 作れる 正方形の 数を 返します。 正方形の 辺は x 軸と y 軸に そろっていて、 面積は 0 より 大きい ものだけを 数えます。 問いの 点その ものは 置いた 点に 足しません
同じ
メソッドのAdd、Count とnewDetectSquares() で
入力は、ops と、args でops のargs のadd は null です。
- 座標は
どれも 0 以上 1000 以下の整数です - 操作の
回数は 3000 回以下です
入力と出力の例
例 1
(1, 1)、
例 2
(0, 5) に 2 つ置いたので、
例 3
(6, 6) を
面接での聞かれ方
面接では、
- 「点を
足す操作と、 ある 点を 角と する 正方形を 数える 操作を 持つクラスを 作ってください」 - 「同じ
点が 何度も 足されたら?」と、 重複の 数え方を 確かめられます - 書き終えた
あとに 「 countの計算量は?」 「点が とても 多い ときは どうしますか?」と続きます
芯は、
ヒント
まだ開いていません
付箋は 3 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3答えに近い
アルゴリズムのチートシートの「配列と文字列」に、
関わる学び
配列と
まず 考える手順
段 1
置いたcount が
段 2
問いの|px - x| と |py - y| が
段 3
点ごとのcnt にcount では、cnt[(px, py)] × cnt[(x, py)] × cnt[(px, y)] を
解答
Python
Python
class DetectSquares:
def __init__(self):
self.cnt = {}
def add(self, point):
key = (point[0], point[1])
c = self.cnt.get(key, 0)
self.cnt[key] = c + 1
def count(self, point):
x, y = point
cnt = self.cnt
total = 0
for (px, py), c in cnt.items():
if px == x:
continue
if abs(px - x) != abs(py - y):
continue
a = cnt.get((x, py), 0)
b = cnt.get((px, y), 0)
total += c * a * b
return totalJavaScript
JavaScript
class DetectSquares {
constructor() {
this.cnt = new Map();
this.pts = [];
}
add(point) {
const key = point.join(",");
const c = this.cnt.get(key);
if (c === undefined) {
this.pts.push(point);
}
this.cnt.set(key, (c ?? 0) + 1);
}
at(x, y) {
return this.cnt.get(`${x},${y}`) ?? 0;
}
count(point) {
const [x, y] = point;
let total = 0;
for (const [px, py] of this.pts) {
const dx = Math.abs(px - x);
const dy = Math.abs(py - y);
if (dx === 0 || dx !== dy) {
continue;
}
total +=
this.at(px, py) *
this.at(x, py) *
this.at(px, y);
}
return total;
}
}Go
Go
type DetectSquares struct {
cnt map[[2]int]int
}
func newDetectSquares(
) *DetectSquares {
m := map[[2]int]int{}
return &DetectSquares{cnt: m}
}
func (d *DetectSquares) Add(
point []int,
) {
key := [2]int{point[0], point[1]}
d.cnt[key]++
}
func abs(v int) int {
if v < 0 {
return -v
}
return v
}
func (d *DetectSquares) Count(
point []int,
) int {
x, y := point[0], point[1]
total := 0
for k, c := range d.cnt {
px, py := k[0], k[1]
if px == x {
continue
}
if abs(px-x) != abs(py-y) {
continue
}
a := d.cnt[[2]int{x, py}]
b := d.cnt[[2]int{px, y}]
total += c * a * b
}
return total
}JavaScript の Map は"1,4" のようなpts には
計算量
addは O(1) です。辞書の 数を 1 つ増やすだけです countは、置いた 点の 違う 場所の 数を q として O(q) です。 場所ごとに 辞書を 2 回引くだけです - 使う
記憶は、 違う 場所の 数の O(q) です - 座標が 0 以上 1000 以下と
小さいので、 x ごとに y の 数を 持つ辞書に して、 問いの 点と 同じ x の 点だけを 見る 書き方も あります。 その ときは 問いの 点の 列に ある 点の 数だけの 手間で済みます
よく ある間違い
- 重複を 1 回しか
数えない。 点を 集合で 持つと、 例 2 で (0, 5) の 2 つ目が 消え、 答えが 1 に なります。 場所ごとに 置いた 数を 持ち、 積を足します - 面積 0 を数える。
|px - x|と|py - y|がどちらも 0 の 場所、 つまり 問いの 点と 同じ 場所を 向かいの 角に 選ぶと、 つぶれた 正方形を 数えてしまいます。 px == xの場所は 飛ばします - 向かいの
角を 1 つの 向きだけで探す。 右上だけを 見ると、 例 3 の 左下に 広がる 正方形を 落とします。 絶対値で 比べれば、 4 つの 向きを 全部拾えます - JavaScript の
Mapに並びを 鍵で入れる。 [1, 4]を 2 回作ると別の 鍵に なり、 数が いつも 0 か 1 のままです
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「軸に
そろった 正方形は、 問いの 点と 向かいの 角を 決めれば、 残りの 2 つの 角が 決まります。 点ごとの 置いた 数を 辞書に 持ち、 countでは置いた 場所を 全部向かいの 角として 試し、 x の 差と y の 差が 等しく 0 でなければ、 3 つの 角の 数の 積を 足します。 addは O(1)、countは置いた 場所の 数に 比例します。」
書き終えたら、
「例 2 を
たどります。 問いの 点は (5, 0) です。 向かいの 角の 候補の うち (0, 5) は x の 差も y の 差も 5 なので 正方形に なります。 (0, 5) は 2 個、 残りの 角 (5, 5) と (0, 0) は 1 個ずつなので、 2 × 1 × 1 で 2 です。 (0, 0) と (5, 5) は 差が そろわないので 数えません。 答えは 2 です。」