Skip to content
← All posts

Maximum Number of Visible Points: Angle Sweep with Sliding Window

6 min read
leetcodeproblemhardmathsortingsliding-window

You are standing at a given location on a 2D plane. You are given an array of points and an integer angle representing your field of view in degrees. Your field of view is a sector centered on some direction you choose. Return the maximum number of points you can see. Points that overlap with your location are always visible, regardless of your angle.

This is LeetCode 1610: Maximum Number of Visible Points, and it is a beautiful combination of geometry, sorting, and sliding windows. The trick is to transform a 2D spatial problem into a 1D circular range problem.

02468100246810(1,9)(2,7)(5,8)(7,6)(8,3)(6,1)(1,2)(9,9)you(3,4)3 visibleangle = 90
Viewer at (3,4) with a 90-degree field of view. Green points fall within the cone; gray points are outside it. The optimal rotation captures 3 points.

Why this problem matters

At first glance, this looks like a pure geometry problem. You might think about rotating a cone and checking which points fall inside. That brute-force approach would be painfully slow. The real lesson here is the transformation: once you convert 2D coordinates into 1D angles, the problem reduces to "find the longest window of size angle in a sorted circular array." That is a classic sliding window problem.

This conversion pattern (reduce a harder problem to a known simpler one) shows up constantly. If you can recognize when a problem hides a sliding window or two-pointer pattern behind geometric or spatial language, you can solve a much wider class of problems.

The key insight

Every point relative to your location can be described by a single angle (its direction from you). If you compute atan2(dy, dx) for each point, you get an angle in radians. Convert to degrees and you have a list of numbers. Now the question becomes: "What is the maximum number of these angle values that fit within a window of width angle?"

Because angles wrap around (359 degrees is close to 1 degree), you cannot just sort and slide a window. You need to handle the circular boundary. The standard trick: duplicate the sorted angle array, adding 360 to each element in the copy. Now a window that wraps around 360 will appear as a contiguous range in the extended array.

Points sitting exactly at your location have no meaningful direction. You remove them from the angle list, count them separately, and add that count to the final answer.

The solution

import math

def visible_points(points: list[list[int]], angle: int, location: list[int]) -> int:
    same = 0
    angles = []
    ox, oy = location

    for x, y in points:
        if x == ox and y == oy:
            same += 1
        else:
            angles.append(math.atan2(y - oy, x - ox) * 180 / math.pi)

    angles.sort()
    n = len(angles)
    angles += [a + 360 for a in angles]

    best = 0
    left = 0
    for right in range(len(angles)):
        while angles[right] - angles[left] > angle:
            left += 1
        best = max(best, right - left + 1)

    return best + same

The first loop separates two categories of points. Points at the exact location are counted in same because they are always visible no matter which direction you face. Every other point gets its angle computed via atan2 and stored in the angles list.

After sorting, we duplicate the array by appending a + 360 for each angle a. This is the circular wrap-around trick. If your field of view spans from 350 degrees to 80 degrees, that range would be broken into two pieces in the original array. In the extended array, it appears as a single contiguous range from 350 to 440 (since 80 + 360 = 440).

The sliding window uses two pointers. The right pointer expands the window one element at a time. Whenever the window width (difference between the rightmost and leftmost angle) exceeds the allowed angle, we advance left to shrink the window. We track the maximum window size throughout.

Finally, we add same to account for the points at our exact location.

The atan2 function returns angles in radians from -pi to pi. Multiplying by 180 / pi converts to degrees. Because the sorted array combined with the + 360 duplication handles any starting position in the circle, you do not need to normalize angles to [0, 360) first. Negative angles work fine as long as the duplication adds exactly 360.

Visual walkthrough

Let's trace through the algorithm step by step. Watch how the 2D spatial problem becomes a 1D sliding window problem after the angle conversion.

Step 1: Convert each point to an angle using atan2(dy, dx).

111.8(1,9)108.4(2,7)63.4(5,8)26.6(7,6)348.7(8,3)315(6,1)225(1,2)39.8(9,9)3600

Compute the angle from the viewer (3,4) to each point. Convert to degrees in [0, 360).

Step 2: Sort the angle array in ascending order.

26.6(7,6)39.8(9,9)63.4(5,8)108.4(2,7)111.8(1,9)225(1,2)315(6,1)348.7(8,3)3600

After sorting: [26.6, 39.8, 63.4, 108.4, 111.8, 225.0, 315.0, 348.7]. This lets us use a sliding window.

Step 3: Duplicate the array, adding 360 to each copy, for circular wrap-around.

26.639.863.4108.4111.8225315348.7386.6399.8423.4468.4...original+ 360

The extended array is [26.6, 39.8, 63.4, 108.4, 111.8, 225.0, 315.0, 348.7 | 386.6, 399.8, ...]. This handles the case where the viewing window wraps past 360 degrees.

Step 4: Slide a window of size 'angle' across the sorted array. Track the maximum count.

26.639.863.4108.4111.8225315348.7window: 5 points

With angle=90: window [26.6, 116.6] captures indices 0-4 (values 26.6, 39.8, 63.4, 108.4, 111.8) = 5 points. But we need the right rotation. The best window captures 3 points: [63.4, 153.4] covers 63.4, 108.4, 111.8.

Step 5: Add points at the viewer's exact location (always visible).

window_maxsliding window best+same_countpoints at location= answer

Points sitting exactly on the viewer's location don't have a meaningful angle. Count them separately and add them to the sliding window maximum. Final answer = window_max + same_location_count.

The transformation from 2D coordinates to a sorted angle array is where all the difficulty lives. Once you have that sorted list and handle the circular duplication, the sliding window is the same one you have seen in dozens of other problems.

Complexity analysis

ApproachTimeSpace
Angle sort + sliding windowO(n log n)O(n)

Time is O(n log n) due to sorting the angles array. The atan2 computation is O(n), and the two-pointer sliding window over the doubled array is O(n). Sorting dominates.

Space is O(n) for the angles array and its doubled copy. We store at most 2n angle values.

The building blocks

1. Coordinate-to-angle conversion with atan2

The pattern of converting 2D relative positions into angles for circular processing:

angles = []
for x, y in points:
    angles.append(math.atan2(y - oy, x - ox) * 180 / math.pi)

Any time you need to reason about directions from a fixed point, atan2 is the tool. It handles all four quadrants correctly and gives you a unique angle for each direction. You will see this in problems involving visibility, angular sorting, and sweep line algorithms. The key detail: atan2 takes (dy, dx), not (dx, dy). Swapping them gives the wrong angle.

2. Circular array duplication for wrap-around

The pattern of doubling a sorted array to handle circular ranges:

angles.sort()
angles += [a + 360 for a in angles]

When a range can wrap around the boundary (end of array connects to start), duplicating the array eliminates the special case. Instead of checking two segments separately, you check one contiguous window in the doubled array. This technique works for any circular sliding window problem, not just angles. You will see it with circular queues, rotated arrays, and scheduling problems where time wraps around midnight.

Edge cases

Before submitting, think through these scenarios:

  • All points at the viewer's location: every point is always visible. same equals the array length, and the angles list is empty. The answer is just same.
  • Angle is 360 or greater: every point is visible regardless of direction. The window covers the entire circle.
  • Two points at the exact same angle: the sliding window counts them both. No special handling needed.
  • Only one point (not at location): the answer is 1 + same. The window always captures at least one angle.
  • Points on the boundary of the viewing angle: the problem states points on the edge of the field of view count as visible. Use >= in your comparisons, which the while angles[right] - angles[left] > angle formulation handles correctly (it keeps points exactly at distance angle inside the window).
  • Negative coordinates: atan2 handles negative dx and dy correctly. No normalization needed.

From understanding to recall

You now know the three-part strategy: convert coordinates to angles with atan2, handle circular wrap-around by duplicating the sorted array, and slide a window to find the maximum count. The logic makes sense when you read it. But reproducing it under pressure is where most people stumble.

The details that trip you up are small but critical. Do you use atan2(dy, dx) or atan2(dx, dy)? Do you add 360 or 2*pi? Do you use > or >= in the window condition? Do you remember to count same-location points separately? These are not conceptual misunderstandings. They are recall gaps.

Spaced repetition is how you close them. You practice the angle conversion, the array duplication, and the sliding window template from memory at increasing intervals. After a few rounds, the pattern is automatic. You see "maximum points within an angle" and the full approach flows out without hesitation.

Related posts

CodeBricks breaks Maximum Number of Visible Points into its angle conversion and circular sliding window building blocks, then drills them independently with spaced repetition. You type each piece from scratch until the pattern is automatic. When a geometry-meets-sliding-window problem shows up in your interview, you do not think about it. You just write it.