Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1610. Maximum Number of Visible Points - Leetcode Solution

Problem Description

You are given a list of points on a 2D plane, represented as points, where each point is an array [x, y]. There is also your location on the plane, given as location = [x0, y0], and an angle in degrees.

You can rotate your viewing direction freely (think of standing at your location and turning your head), but your field of view is a sector of size angle degrees. A point is visible if the angle between your viewing direction and the point is less than or equal to angle/2 on either side.

The goal is to return the maximum number of points you can see at once by rotating your viewing direction appropriately. Points at your location are always visible, regardless of the direction or angle.

  • Constraints:
    • 1 ≤ points.length ≤ 105
    • points[i].length == 2
    • location.length == 2
    • 0 ≤ angle ≤ 360
    • All coordinates are integers

Thought Process

At first glance, you might think about checking every possible viewing direction and counting how many points are visible within the given angle. However, with up to 105 points, this brute-force approach would be far too slow.

Instead, let's reframe the problem: for each point (except those at your location), compute its angle relative to your location. Then, the problem becomes: "What is the largest number of angles that fit within any window of size angle degrees on the circle?"

This is similar to finding the longest subarray (window) with a difference between the first and last element ≤ angle, but with a circular domain (angles wrap from 359° back to 0°).

We can use a sliding window technique after sorting the angles, and handle the circularity by duplicating the angle list with each angle increased by 360°.

Solution Approach

  • Step 1: Handle Points at Your Location
    • Count all points that are exactly at location. These are always visible, so we can treat them separately.
  • Step 2: Compute Angles for All Other Points
    • For each point not at your location, compute the angle (in degrees) from your location to the point using atan2(y, x).
    • Convert the angle from radians to degrees and normalize it to [0, 360).
  • Step 3: Sort and Duplicate Angles
    • Sort the list of angles.
    • To handle the circular nature (e.g., 359° and 1° are only 2° apart), append each angle + 360° to the end of the list.
  • Step 4: Sliding Window
    • Use two pointers to find the largest window where the difference between the right and left angles is ≤ angle.
    • Keep track of the maximum number of points in any window.
  • Step 5: Combine Results
    • The answer is the maximum window size plus the number of points at your location.

This approach is efficient because sorting takes O(n log n), and the sliding window is O(n).

Example Walkthrough

Example Input:
points = [[2,1],[2,2],[3,3]], angle = 90, location = [1,1]

  • Step 1: None of the points are at location [1,1], so same_location = 0.
  • Step 2: Compute angles:
    • [2,1] → dx=1, dy=0 → angle=0°
    • [2,2] → dx=1, dy=1 → angle=45°
    • [3,3] → dx=2, dy=2 → angle=45°
    So, angles = [0, 45, 45]
  • Step 3: Sort and duplicate:
    • Sorted: [0, 45, 45]
    • Duplicated: [0, 45, 45, 360, 405, 405]
  • Step 4: Sliding window:
    • Start at 0: window [0,45,45] → all within 90° → 3 points
    • Check other windows, but none are larger.
  • Step 5: Add points at location (0) → answer is 3.

Result: You can see all 3 points within a 90° field of view at the right direction.

Code Implementation

import math

class Solution:
    def visiblePoints(self, points, angle, location):
        same_location = 0
        angles = []
        x0, y0 = location
        for x, y in points:
            if x == x0 and y == y0:
                same_location += 1
            else:
                dx, dy = x - x0, y - y0
                theta = math.degrees(math.atan2(dy, dx))
                if theta < 0:
                    theta += 360
                angles.append(theta)
        angles.sort()
        n = len(angles)
        # Duplicate the angles to handle wrap-around
        angles += [a + 360 for a in angles]
        max_cnt = left = 0
        for right in range(len(angles)):
            while angles[right] - angles[left] > angle:
                left += 1
            max_cnt = max(max_cnt, right - left + 1)
        return max_cnt + same_location
      
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;

class Solution {
public:
    int visiblePoints(vector<vector<int>>& points, int angle, vector<int>& location) {
        int same_location = 0;
        vector<double> angles;
        int x0 = location[0], y0 = location[1];
        for (auto& p : points) {
            int x = p[0], y = p[1];
            if (x == x0 && y == y0) {
                same_location++;
            } else {
                double theta = atan2(y - y0, x - x0) * 180.0 / M_PI;
                if (theta < 0) theta += 360.0;
                angles.push_back(theta);
            }
        }
        sort(angles.begin(), angles.end());
        int n = angles.size();
        for (int i = 0; i < n; ++i) {
            angles.push_back(angles[i] + 360.0);
        }
        int max_cnt = 0, left = 0;
        for (int right = 0; right < angles.size(); ++right) {
            while (angles[right] - angles[left] > angle) {
                left++;
            }
            max_cnt = max(max_cnt, right - left + 1);
        }
        return max_cnt + same_location;
    }
};
      
import java.util.*;

class Solution {
    public int visiblePoints(List<List<Integer>> points, int angle, List<Integer> location) {
        int sameLocation = 0;
        List<Double> angles = new ArrayList<>();
        int x0 = location.get(0), y0 = location.get(1);
        for (List<Integer> p : points) {
            int x = p.get(0), y = p.get(1);
            if (x == x0 && y == y0) {
                sameLocation++;
            } else {
                double theta = Math.toDegrees(Math.atan2(y - y0, x - x0));
                if (theta < 0) theta += 360.0;
                angles.add(theta);
            }
        }
        Collections.sort(angles);
        int n = angles.size();
        List<Double> allAngles = new ArrayList<>(angles);
        for (double a : angles) {
            allAngles.add(a + 360.0);
        }
        int maxCnt = 0, left = 0;
        for (int right = 0; right < allAngles.size(); right++) {
            while (allAngles.get(right) - allAngles.get(left) > angle) {
                left++;
            }
            maxCnt = Math.max(maxCnt, right - left + 1);
        }
        return maxCnt + sameLocation;
    }
}
      
var visiblePoints = function(points, angle, location) {
    let sameLocation = 0;
    const angles = [];
    const [x0, y0] = location;
    for (const [x, y] of points) {
        if (x === x0 && y === y0) {
            sameLocation++;
        } else {
            let theta = Math.atan2(y - y0, x - x0) * 180 / Math.PI;
            if (theta < 0) theta += 360;
            angles.push(theta);
        }
    }
    angles.sort((a, b) => a - b);
    const n = angles.length;
    const allAngles = angles.concat(angles.map(a => a + 360));
    let maxCnt = 0, left = 0;
    for (let right = 0; right < allAngles.length; right++) {
        while (allAngles[right] - allAngles[left] > angle) {
            left++;
        }
        maxCnt = Math.max(maxCnt, right - left + 1);
    }
    return maxCnt + sameLocation;
};
      

Time and Space Complexity

  • Brute-force approach:
    • For each possible direction, count how many points are visible within the angle. This would be O(n2), which is too slow for large n.
  • Optimized approach (as above):
    • Computing angles: O(n)
    • Sorting angles: O(n log n)
    • Duplicating and sliding window: O(n)
    • Total time complexity: O(n log n)
    • Space complexity: O(n) for storing angles and their duplicates.

This efficiency is possible thanks to sorting and the sliding window, both of which are much faster than brute-force for large inputs.

Summary

The key insight for this problem is to reduce the geometric challenge to a problem about angles and intervals. By converting points to angles and using a sliding window over the sorted (and duplicated) list, we can efficiently find the maximum number of visible points. Handling points at your location separately ensures correctness. This approach is both elegant and optimal for large datasets.