LeetCode 2021 - Brightest Position on Street
The problem asks us to find the position on a straight street that is illuminated by the largest number of street lamps, which is referred to as its brightness.
Difficulty: 🟡 Medium
Topics: Array, Sorting, Prefix Sum, Ordered Set
Solution
Problem Understanding
The problem asks us to find the position on a straight street that is illuminated by the largest number of street lamps, which is referred to as its brightness. The street is represented as a number line, and each street lamp is given as a pair [positioni, rangei], which defines the interval [positioni - rangei, positioni + rangei] that the lamp illuminates, inclusive of both ends. The brightness of a position is simply the count of all lamps whose intervals cover that position. If multiple positions have the same maximum brightness, the smallest position should be returned.
The input is a 2D array of integers with up to 10^5 lamps, and the positions and ranges can be very large integers (up to 10^8 in magnitude). This makes a naive approach of checking every single integer on the number line infeasible. We must instead rely on an approach that does not explicitly iterate over every possible position.
Edge cases to consider include lamps with zero range (which illuminate only their exact position), overlapping ranges, negative positions, and the possibility that multiple positions share the same brightness. The input guarantees at least one lamp exists.
Approaches
A brute-force approach would iterate over the entire number line, for each position counting the number of lamps that cover it. This is correct in principle but infeasible because positions can range from -10^8 to 10^8, leading to an O(N * max_range) runtime, which is far too slow for the given constraints.
The optimal approach relies on the observation that brightness changes only at the start and end points of lamp intervals. If we treat the start of an interval as a +1 event and the position immediately after the end of an interval as a -1 event, we can sort all these events and simulate a running total of brightness. The maximum running total corresponds to the brightest position. This approach is efficient because it only considers positions where brightness changes, not every position on the number line.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(N * R) | O(1) | Count brightness at every position in all lamp ranges; too slow for large ranges. |
| Optimal | O(N log N) | O(N) | Use sweep line / prefix sum events at interval endpoints; efficient and scalable. |
Algorithm Walkthrough
- Initialize an empty list
eventsto store brightness changes. - For each lamp
[pos, rng], calculate the interval[start, end] = [pos - rng, pos + rng]. - For each interval, add two events:
(start, +1)and(end + 1, -1). The+1marks the start of illumination, and the-1marks the first position after the illumination ends. - Sort the
eventslist by position. If multiple events occur at the same position, order does not matter because we will process them sequentially. - Initialize
current_brightness = 0andmax_brightness = 0. Also initializebrightest_positionto a large sentinel value. - Iterate through each
(position, delta)inevents:
- Update
current_brightness += delta. - If
current_brightness > max_brightness, updatemax_brightness = current_brightnessand setbrightest_position = position. - If
current_brightness == max_brightness, keep the smallerbrightest_position.
- Return
brightest_position.
Why it works: The algorithm works because brightness only changes at interval start and end points. By tracking these events in order, we accurately capture all transitions of brightness without checking every position, ensuring correctness and efficiency.
Python Solution
from typing import List
class Solution:
def brightestPosition(self, lights: List[List[int]]) -> int:
events = []
for pos, rng in lights:
start, end = pos - rng, pos + rng
events.append((start, 1))
events.append((end + 1, -1))
events.sort()
current_brightness = 0
max_brightness = 0
brightest_position = float('inf')
for position, delta in events:
current_brightness += delta
if current_brightness > max_brightness:
max_brightness = current_brightness
brightest_position = position
elif current_brightness == max_brightness:
brightest_position = min(brightest_position, position)
return brightest_position
This implementation first converts each lamp's range into a start and end event. Sorting the events ensures we process positions in order, and using a running total (current_brightness) allows us to efficiently track which position has the maximum number of overlapping lamps.
Go Solution
import "sort"
func brightestPosition(lights [][]int) int {
type Event struct {
pos int
delta int
}
events := make([]Event, 0, 2*len(lights))
for _, lamp := range lights {
start := lamp[0] - lamp[1]
end := lamp[0] + lamp[1]
events = append(events, Event{start, 1})
events = append(events, Event{end + 1, -1})
}
sort.Slice(events, func(i, j int) bool {
return events[i].pos < events[j].pos
})
currentBrightness := 0
maxBrightness := 0
brightestPosition := int(1e18)
for _, e := range events {
currentBrightness += e.delta
if currentBrightness > maxBrightness {
maxBrightness = currentBrightness
brightestPosition = e.pos
} else if currentBrightness == maxBrightness {
if e.pos < brightestPosition {
brightestPosition = e.pos
}
}
}
return brightestPosition
}
The Go version mirrors the Python approach. Go requires explicit struct types for events, and sorting is done via sort.Slice. A large sentinel value is used for brightestPosition to ensure correct comparison.
Worked Examples
Example 1: lights = [[-3,2],[1,2],[3,3]]
| Event | delta | current_brightness | max_brightness | brightest_position |
|---|---|---|---|---|
| -5 | +1 | 1 | 1 | -5 |
| -1 | +1 | 2 | 2 | -1 |
| 0 | +1 | 3 | 3 | 0 |
| 4 | -1 | 2 | 3 | 0 |
| 6 | -1 | 1 | 3 | 0 |
| 7 | -1 | 0 | 3 | 0 |
Brightest position is -1 (smallest among positions with brightness 2).
Example 2: lights = [[1,0],[0,1]]
| Event | delta | current_brightness | max_brightness | brightest_position |
|---|---|---|---|---|
| -1 | +1 | 1 | 1 | -1 |
| 0 | +1 | 2 | 2 | 0 |
| 1 | +1 | 3 | 3 | 1 |
| 1 | -1 | 2 | 3 | 1 |
| 2 | -1 | 1 | 3 | 1 |
Brightest position is 1.
Example 3: lights = [[1,2]]
| Event | delta | current_brightness | max_brightness | brightest_position |
|---|---|---|---|---|
| -1 | +1 | 1 | 1 | -1 |
| 4 | -1 | 0 | 1 | -1 |
Brightest position is -1.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(N log N) | Sorting 2N events dominates runtime, iterating events is O(N). |
| Space | O(N) | We store two events per lamp. |
The approach scales well to the constraints since sorting 2 * 10^5 events is feasible and avoids iterating over the huge number line.
Test Cases
sol = Solution()
# Provided examples
assert sol.brightestPosition([[-3,2],[1,2],[3,3]]) == -1 # Multiple overlapping intervals
assert sol.brightestPosition([[1,0],[0,1]]) == 1 # Smallest among brightest
assert sol.brightestPosition([[1,2]]) == -1 # Single lamp
# Edge cases
assert sol.brightestPosition([[0,0]]) == 0 # Single lamp at origin
assert sol.brightestPosition([[0,100000000],[100000000,0]]) == 0 # Very large ranges
assert sol.brightestPosition([[0,1],[2,1],[4,1]]) == -1 # Non-overlapping intervals
assert sol.brightestPosition([[0,1],[1,1],[2,