Куча
K ближайших точек
Найти k ближайших точек к началу координат
Постановка задачи
Описание (LeetCode - 973. K Closest Points to Origin)
Дан массив точек points, где points[i] = [xi, yi], и число k.
Нужно вернуть k точек, которые находятся ближе всего к началу координат
(0, 0).
Расстояние от точки до начала координат считается по формуле:
sqrt(x^2 + y^2)
Ответ можно вернуть в любом порядке.
Пример 1:
points = [[1, 3], [-2, 2]]
k = 1
Результат:
[[-2, 2]]
Пояснение:
Расстояние от [1, 3] до начала координат равно sqrt(10).
Расстояние от [-2, 2] равно sqrt(8), поэтому эта точка ближе.
Пример 2:
points = [[3, 3], [5, -1], [-2, 4]]
k = 2
Результат:
[[3, 3], [-2, 4]]
Ограничения:
1 <= k <= points.length <= 10⁴-10⁴ <= xi, yi <= 10⁴- ответ гарантированно уникален без учета порядка элементов
Решение
Можно посчитать расстояние для каждой точки, отсортировать все точки и взять
первые k. Это работает за O(n log n), но сортировать весь массив избыточно,
так как нам нужны только k ближайших точек.
Идея решения:
Будем поддерживать максимальную кучу размера k. В ней хранятся текущие k
ближайших точек. Вершина кучи - самая дальняя точка среди выбранных кандидатов.
Когда мы берем очередную точку, добавляем ее в кучу. Если размер кучи стал больше
k, удаляем вершину (самую дальнюю точку). После этого в куче снова остаются
k ближайших точек из всех просмотренных.
Нам не нужно вычислять квадратный корень sqrt(x^2 + y^2). Для сравнения
расстояний достаточно использовать квадрат расстояния x^2 + y^2. Если одно
расстояние меньше другого, то и квадрат этого расстояния тоже меньше.
Основные шаги:
- Создаем максимальную кучу по квадрату расстояния.
- Проходим по всем точкам.
- Добавляем текущую точку в кучу.
- Если размер кучи больше
k, удаляем самую дальнюю точку. - Возвращаем все точки, оставшиеся в куче.
Рассмотрим пример:
points = [[3, 3], [5, -1], [-2, 4]]
k = 2
Квадраты расстояний:
[3, 3] -> 18
[5, -1] -> 26
[-2, 4] -> 20
- Добавляем
[3, 3], куча хранит одну точку.
- Добавляем
[5, -1], куча хранит две точки.
- Добавляем
[-2, 4], размер стал3. Удаляем самую дальнюю точку[5, -1]с квадратом расстояния26.
В куче остались [3, 3] и [-2, 4].
Максимальная куча размера k часто используется, когда нужно найти k
минимальных элементов. Мы держим лучших кандидатов и быстро удаляем худшего
среди них.
Оценка сложности
Временная сложность
Мы обрабатываем n точек. Для каждой точки выполняем добавление в кучу и,
возможно, удаление. Размер кучи не превышает k, поэтому каждая операция
занимает O(log k).
Итоговая временная сложность: O(n log k).
Пространственная сложность
Куча хранит не более k точек, поэтому дополнительная память равна O(k).
Код решения
Приведем код решения.
import "github.com/emirpasic/gods/trees/binaryheap"
type Point struct {
x int
y int
dist int
}
func kClosest(points [][]int, k int) [][]int {
heap := binaryheap.NewWith(func(a, b interface{}) int {
return b.(Point).dist - a.(Point).dist
})
for _, point := range points {
x, y := point[0], point[1]
heap.Push(Point{x: x, y: y, dist: x*x + y*y})
if heap.Size() > k {
heap.Pop()
}
}
result := [][]int{}
for !heap.Empty() {
val, _ := heap.Pop()
point := val.(Point)
result = append(result, []int{point.x, point.y})
}
return result
}Итоги
Задача K Closest Points to Origin показывает прием с максимальной кучей: чтобы
найти k минимальных элементов, удобно поддерживать максимальную кучу размера
k.
- Суть алгоритма: добавляем точки в максимальную кучу и удаляем самую
дальнюю, если кандидатов стало больше
k. - Эффективность:
- Временная сложность:
O(n log k). - Пространственная сложность:
O(k).
- Временная сложность:
- Граничные случаи:
k = points.length: в ответ попадут все точки.- Отрицательные координаты: квадрат расстояния
x^2 + y^2всегда неотрицателен, поэтому знаки координат не влияют на корректность вычислений.
- Оптимальное решение: мы не сортируем все точки. Куча размера
kхранит только ближайших кандидатов и сразу отбрасывает лишние элементы.