2013. Detect Squares
MediumDescription
設計一個資料結構,支援兩個操作:
add(point)加入一個點,count(point)計算與給定點能組成軸對齊正方形的方式數。正方形的邊必須平行於 x 軸和 y 軸,且面積大於 0。
Example:
Input: [“DetectSquares”,“add”,“add”,“add”,“count”] Output: [[],[],[],[],[1]] (加入 [3,10],[11,2],[3,2] 後,count([11,10]) 回傳 1)
Intuition#
核心思路:固定查詢點和一個對角點,用邊長推算另外兩個角的位置,查 Hash Map 確認是否存在。
- 軸對齊正方形由對角線上的兩個點可以唯一確定另外兩個點
- 給定查詢點 (px, py),枚舉所有與它 x 座標相同的點 (px, qy) 作為同一邊
- 邊長 side = |py - qy|,另外兩個角在 (px + side, py) 和 (px + side, qy) 或 (px - side, py) 和 (px - side, qy)
Approaches#
1: 暴力枚舉所有點對#
- 概念: 對每個查詢,枚舉所有已加入的點對,檢查能否與查詢點構成正方形
- 時間複雜度: add
O(1),countO(n^2)- n 為已加入的點數 - 空間複雜度:
O(n)
class DetectSquares() {
private val points = mutableListOf<IntArray>()
fun add(point: IntArray) {
points.add(point)
}
fun count(point: IntArray): Int {
val (px, py) = point
var count = 0
for (p1 in points) {
for (p2 in points) {
if (p1[0] == px && p2[1] == py &&
Math.abs(p1[1] - py) == Math.abs(p2[0] - px) &&
p1[1] != py && p2[0] != px &&
p1[1] == p2[1] && Math.abs(p2[0] - px) > 0) {
// 不好寫也容易錯,見下方最佳解
}
}
}
return count
}
}⭐2: Hash Map + 枚舉對角點#
- 概念: 用 Hash Map 記錄每個座標出現的次數。枚舉所有與查詢點 y 座標不同的已知點作為對角點,計算另外兩個角是否存在
- 時間複雜度: add
O(1),countO(n)- n 為不重複的點數 - 空間複雜度:
O(n)
class DetectSquares() {
private val pointCount = HashMap<Long, Int>()
private val pointsByX = HashMap<Int, MutableList<IntArray>>()
private fun encode(x: Int, y: Int): Long = x.toLong() * 100001 + y
fun add(point: IntArray) {
val key = encode(point[0], point[1])
pointCount[key] = pointCount.getOrDefault(key, 0) + 1
pointsByX.getOrPut(point[0]) { mutableListOf() }.add(point)
}
fun count(point: IntArray): Int {
val (px, py) = point
var result = 0
// 枚舉與 px 相同 x 座標的點,作為正方形的同一條垂直邊
val sameX = pointsByX[px] ?: return 0
for (p in sameX) {
val qy = p[1]
if (qy == py) continue // 邊長不能為 0
val side = qy - py
// 正方形的另外兩個角
val c1 = pointCount.getOrDefault(encode(px + side, py), 0)
val c2 = pointCount.getOrDefault(encode(px + side, qy), 0)
result += c1 * c2
val c3 = pointCount.getOrDefault(encode(px - side, py), 0)
val c4 = pointCount.getOrDefault(encode(px - side, qy), 0)
result += c3 * c4
}
return result
}
}注意重複點的處理:同一個座標可以被加入多次,因此用 count 來計數,結果要乘上各角的出現次數。
補充:另一種枚舉策略
也可以枚舉對角點(x, y 都不同於查詢點且構成正方形),但需要確保 |dx| == |dy|:
class DetectSquares() {
private val countMap = HashMap<Long, Int>()
private val allPoints = mutableListOf<IntArray>()
private fun encode(x: Int, y: Int): Long = x.toLong() * 100001 + y
fun add(point: IntArray) {
val key = encode(point[0], point[1])
countMap[key] = countMap.getOrDefault(key, 0) + 1
allPoints.add(point)
}
fun count(point: IntArray): Int {
val (px, py) = point
var result = 0
for (p in allPoints) {
val (x, y) = p
// 對角點必須 x != px, y != py, 且 |dx| == |dy|
if (Math.abs(x - px) != Math.abs(y - py) || x == px || y == py) continue
result += countMap.getOrDefault(encode(x, py), 0) *
countMap.getOrDefault(encode(px, y), 0)
}
return result
}
}這種方法每次 count 遍歷所有已加入的點(含重複),但避免了重複計數的問題。
🔑 Takeaways#
- Pattern: 幾何題用 Hash Map 儲存點的出現次數,枚舉部分頂點推導其餘頂點
- 關鍵技巧: 軸對齊正方形的性質——固定一條邊就能推出整個正方形;座標編碼成單一 key 方便查找