Ежедневная часть (e) C++ # 107, Распространенная проблема на собеседовании: видимые точки

Учитывая список точек на 2D-плоскости, местоположение и угол [0..360], вернуть максимальное количество точек, которые можно увидеть из местоположения, используя поле зрения с шириной, заданной углом.
Точки не мешают друг другу, включая точки, находящиеся в одном и том же положении.

Прежде чем вы продолжите чтение, я рекомендую вам попробовать решить ее самостоятельно. Вот ссылка на Compiler Explorer с парой тестов: https://compiler-explorer.com/z/6cvaosPMY.
Решение
Давайте сначала рассмотрим, что мы пытаемся сделать здесь.
Мы хотим перебирать наборы точек, где угол между «самой левой» и «самой правой» точкой меньше (или равен) полю зрения (наша позиция — это начало координат).
Поэтому первым шагом будет вычисление этих углов. Мы можем использовать std::atan2, который удобно обрабатывает угловые случаи, когда мы делим на ноль (для точек с одной и той же координатой x). Возвращаем угол в радианах.
После того, как мы вычислили и отсортировали все углы, нам нужно только найти окно в данных, где угол между крайним левым и крайним правым элементом меньше или равен нашему полю зрения и содержит максимальное количество элементов. Мы можем сделать это с помощью скользящего окна.
Есть пара угловых случаев, с которыми нам нужно разобраться.
- мы всегда видим точки, на которых стоим
- наше поле зрения может охватывать конец массива рассчитанных углов, что мы можем исправить, продублировав массив, добавив 2*pi к каждому элементу
- дублирование вводит еще одну проблему, когда для поля зрения в 360 градусов мы дважды считаем элементы с минимальным углом
Наконец, мы возвращаем количество точек, на которых мы стоим, плюс размер максимального окна, которое мы нашли во время поиска в скользящем окне.
#include <vector>
#include <numbers>
#include <cmath>
#include <cstdint>
#include <algorithm>
struct Point {
int x;
int y;
};
int visible_points(std::vector<Point>& points,
int angle, Point location) {
// With angle == 360, we can see all points.
if (angle == 360)
return points.size();
// Transform each point into an angle
// (i.e. direction from location)
std::vector<double> angles;
int base = 0;
for (const auto& p : points) {
int x = p.x-location.x;
int y = p.y-location.y;
if (x == 0 && y == 0)
// we can always see points we are standing on
++base;
else
// atan2 handles the corner cases of x == 0
// returned angle is in radians
angles.push_back(std::atan2(y,x));
}
if (angles.empty())
return base;
// Add another copy of the angles, adding 2*pi to each angle
// this way, we don't have to deal with the field of view
// crossing the boundary
int sz = angles.size();
for (int i = 0; i < sz; ++i)
angles.push_back(angles[i]+2*std::numbers::pi);
std::sort(angles.begin(), angles.end());
// The angle in radians
double rad = std::numbers::pi*angle/180;
// With the preparation done, we can use a simple sliding window
int64_t left = 0, right = 0;
int64_t max = 0;
while (right < std::ssize(angles)) {
// While the angle is less than our field of vision
// - advance the right border
if (angles[right]-angles[left] <= rad) {
max = std::max(max, right-left+1);
++right;
continue;
// While the angle is more than our field of vision
// - advance the left border
} else if (left < right) {
++left;
}
}
// Maximum visible + points we are standing on
return max + base;
}