2237. Count Positions on Street With Required Brightness 🔒
Problem Description
You have a straight street represented as a number line from 0
to n - 1
. There are street lamps placed at various positions along this street.
Each street lamp is described by two values:
position_i
: where the lamp is located on the streetrange_i
: how far the lamp's light reaches in both directions
A lamp at position position_i
with range range_i
illuminates all positions from max(0, position_i - range_i)
to min(n - 1, position_i + range_i)
inclusive. This means the lamp lights up positions within its range but doesn't go beyond the street boundaries (0 and n-1).
The brightness of any position on the street equals the total number of lamps that illuminate that position. For example, if 3 different lamps illuminate position 5, then position 5 has a brightness of 3.
You're given an array requirement
where requirement[i]
specifies the minimum brightness needed at position i
.
Your task is to count how many positions on the street meet or exceed their brightness requirement. In other words, count the positions where the actual brightness is greater than or equal to the required brightness.
Example: If a position needs brightness of 2 (requirement) and 3 lamps illuminate it (actual brightness = 3), this position meets the requirement and should be counted.
The function should return the total number of positions between 0
and n - 1
that satisfy their brightness requirements.
Intuition
The key insight is that we need to efficiently track how many lamps illuminate each position on the street. A naive approach would be to iterate through each lamp and mark every position it illuminates, but this could be inefficient when lamps have large ranges.
Instead, we can use a difference array technique. The idea is that when a lamp illuminates a continuous range of positions, rather than updating each position individually, we only mark the boundaries of the range.
Think of it like this: when a lamp starts illuminating at position i
, we mark "+1 lamp starts here". When the lamp stops illuminating after position j
, we mark "-1 lamp ends here".
For example, if a lamp illuminates positions [2, 5], we put:
+1
at position 2 (a lamp starts illuminating here)-1
at position 6 (a lamp stops illuminating after position 5)
When we later compute the prefix sum of these markers, the running sum at any position tells us exactly how many lamps are illuminating that position. This works because:
- Each
+1
we encounter means a new lamp range has started - Each
-1
we encounter means a lamp range has ended - The cumulative sum gives us the total number of active lamp ranges at each position
This transforms our problem from "update many positions for each lamp" to "update only 2 positions for each lamp", followed by a single pass to compute prefix sums. Once we have the brightness at each position through the prefix sum, we simply compare it with the requirement array to count positions that meet their minimum brightness requirement.
Learn more about Prefix Sum patterns.
Solution Approach
We implement the solution using a difference array technique:
-
Initialize the difference array: Create an array
d
of sizen + 1
(one extra position to handle the boundary). Initialize all values to 0. -
Process each street lamp: For each lamp at position
p
with ranger
:- Calculate the left boundary of illumination:
i = max(0, p - r)
(ensuring we don't go below position 0) - Calculate the right boundary of illumination:
j = min(n - 1, p + r)
(ensuring we don't exceed position n-1) - Mark the start of illumination:
d[i] += 1
- Mark the end of illumination:
d[j + 1] -= 1
- Calculate the left boundary of illumination:
-
Compute actual brightness using prefix sum: Apply the
accumulate
function to the difference arrayd
. This gives us the actual brightness at each position. The prefix sum works because:- Starting from position 0, we accumulate the difference values
- Each
+1
increases the running count (a lamp range starts) - Each
-1
decreases the running count (a lamp range ends) - The accumulated value at position
i
equals the number of lamps illuminating that position
-
Count positions meeting requirements: Compare the actual brightness (from the prefix sum) with the required brightness at each position. Use
zip
to pair up the accumulated brightness values with the requirements, and count how many positions havebrightness >= requirement
.
The final expression sum(s >= r for s, r in zip(accumulate(d), requirement))
elegantly combines steps 3 and 4:
accumulate(d)
generates the brightness valueszip
pairs each brightness with its corresponding requirement- The generator expression checks if each position meets its requirement
sum
counts the total number of positions that satisfy the condition
This approach has a time complexity of O(m + n)
where m
is the number of lamps and n
is the street length, which is much more efficient than the naive O(m * n)
approach.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
- Street length:
n = 7
(positions 0 to 6) - Street lamps:
[[2, 2], [5, 1]]
- Lamp 1: position 2, range 2 (illuminates positions 0-4)
- Lamp 2: position 5, range 1 (illuminates positions 4-6)
- Requirements:
[1, 0, 2, 0, 1, 1, 0]
Step 1: Initialize difference array
Create array d
of size 8 (n+1):
d = [0, 0, 0, 0, 0, 0, 0, 0]
Step 2: Process each lamp
Lamp 1 (position=2, range=2):
- Left boundary: max(0, 2-2) = 0
- Right boundary: min(6, 2+2) = 4
- Mark boundaries: d[0] += 1, d[5] -= 1
d = [1, 0, 0, 0, 0, -1, 0, 0]
Lamp 2 (position=5, range=1):
- Left boundary: max(0, 5-1) = 4
- Right boundary: min(6, 5+1) = 6
- Mark boundaries: d[4] += 1, d[7] -= 1
d = [1, 0, 0, 0, 1, -1, 0, -1]
Step 3: Compute brightness using prefix sum Apply accumulate to get actual brightness at each position:
Position: 0 1 2 3 4 5 6 d values: 1 0 0 0 1 -1 0 Brightness: 1 1 1 1 2 1 1
The prefix sum works as:
- Position 0: 1 (one lamp range starts)
- Position 1: 1 (no change, still in same lamp range)
- Position 2-3: 1 (continuing same lamp range)
- Position 4: 2 (second lamp range starts, now two lamps overlap)
- Position 5: 1 (first lamp range ends, only second lamp remains)
- Position 6: 1 (second lamp continues)
Step 4: Count positions meeting requirements Compare brightness with requirements:
Position: 0 1 2 3 4 5 6 Brightness: 1 1 1 1 2 1 1 Requirement: 1 0 2 0 1 1 0 Meets req?: ✓ ✓ ✗ ✓ ✓ ✓ ✓
- Position 0: 1 >= 1 ✓
- Position 1: 1 >= 0 ✓
- Position 2: 1 < 2 ✗
- Position 3: 1 >= 0 ✓
- Position 4: 2 >= 1 ✓
- Position 5: 1 >= 1 ✓
- Position 6: 1 >= 0 ✓
Result: 6 positions meet their brightness requirements.
Solution Implementation
1from typing import List
2from itertools import accumulate
3
4class Solution:
5 def meetRequirement(
6 self, n: int, lights: List[List[int]], requirement: List[int]
7 ) -> int:
8 # Initialize difference array for range updates
9 # Extra element at index n for handling range end boundaries
10 diff_array = [0] * (n + 1)
11
12 # Process each light source
13 for position, range_radius in lights:
14 # Calculate the leftmost and rightmost positions affected by this light
15 # Ensure boundaries stay within [0, n-1]
16 left_bound = max(0, position - range_radius)
17 right_bound = min(n - 1, position + range_radius)
18
19 # Apply range update using difference array technique
20 # Increment at start of range, decrement after end of range
21 diff_array[left_bound] += 1
22 diff_array[right_bound + 1] -= 1
23
24 # Calculate actual brightness at each position using prefix sum
25 # Then count positions where brightness meets or exceeds requirement
26 brightness_values = accumulate(diff_array)
27 positions_meeting_requirement = sum(
28 brightness >= required
29 for brightness, required in zip(brightness_values, requirement)
30 )
31
32 return positions_meeting_requirement
33
1class Solution {
2 public int meetRequirement(int n, int[][] lights, int[] requirement) {
3 // Create a difference array to track brightness changes
4 // Size is n+1 to handle the boundary case when decrementing at position n
5 int[] differenceArray = new int[n + 1];
6
7 // Process each light source and mark its coverage range
8 for (int[] light : lights) {
9 // Calculate the leftmost position affected by this light
10 // light[0] is position, light[1] is range
11 int leftBoundary = Math.max(0, light[0] - light[1]);
12
13 // Calculate the rightmost position affected by this light
14 int rightBoundary = Math.min(n - 1, light[0] + light[1]);
15
16 // Mark the start of the light's coverage with increment
17 differenceArray[leftBoundary]++;
18
19 // Mark the end of the light's coverage with decrement
20 differenceArray[rightBoundary + 1]--;
21 }
22
23 // Calculate actual brightness at each position using prefix sum
24 int currentBrightness = 0;
25 int positionsMeetingRequirement = 0;
26
27 // Iterate through each position to check if it meets the requirement
28 for (int position = 0; position < n; position++) {
29 // Update current brightness by applying the difference
30 currentBrightness += differenceArray[position];
31
32 // Check if current position meets its brightness requirement
33 if (currentBrightness >= requirement[position]) {
34 positionsMeetingRequirement++;
35 }
36 }
37
38 return positionsMeetingRequirement;
39 }
40}
41
1class Solution {
2public:
3 int meetRequirement(int n, vector<vector<int>>& lights, vector<int>& requirement) {
4 // Use difference array to efficiently track brightness changes
5 // diff[i] represents the change in brightness at position i
6 vector<int> diff(n + 1);
7
8 // Process each light source
9 for (const auto& light : lights) {
10 // Calculate the leftmost position affected by this light
11 // light[0] is the position, light[1] is the range
12 int leftBound = max(0, light[0] - light[1]);
13
14 // Calculate the rightmost position affected by this light
15 int rightBound = min(n - 1, light[0] + light[1]);
16
17 // Mark the start and end of the light's effect using difference array
18 ++diff[leftBound]; // Brightness increases starting from leftBound
19 --diff[rightBound + 1]; // Brightness decreases after rightBound
20 }
21
22 // Calculate actual brightness at each position and count positions meeting requirement
23 int currentBrightness = 0;
24 int positionsMeetingRequirement = 0;
25
26 for (int i = 0; i < n; ++i) {
27 // Update current brightness by accumulating the difference
28 currentBrightness += diff[i];
29
30 // Check if current position meets its brightness requirement
31 if (currentBrightness >= requirement[i]) {
32 ++positionsMeetingRequirement;
33 }
34 }
35
36 return positionsMeetingRequirement;
37 }
38};
39
1/**
2 * Counts the number of positions that meet the brightness requirement
3 * @param n - The number of positions on the street (0 to n-1)
4 * @param lights - Array of [position, radius] pairs representing street lights
5 * @param requirement - Array of minimum brightness requirements for each position
6 * @returns The count of positions that meet their brightness requirement
7 */
8function meetRequirement(n: number, lights: number[][], requirement: number[]): number {
9 // Initialize difference array to track brightness changes
10 // Using n+1 size to handle boundary updates safely
11 const brightnessChanges: number[] = Array(n + 1).fill(0);
12
13 // Process each light and mark its coverage range using difference array technique
14 for (const [position, radius] of lights) {
15 // Calculate the leftmost position this light can illuminate (bounded by 0)
16 const leftBoundary: number = Math.max(0, position - radius);
17
18 // Calculate the rightmost position this light can illuminate (bounded by n-1)
19 const rightBoundary: number = Math.min(n - 1, position + radius);
20
21 // Mark the start of illumination range
22 brightnessChanges[leftBoundary]++;
23
24 // Mark the end of illumination range (position after the last illuminated point)
25 brightnessChanges[rightBoundary + 1]--;
26 }
27
28 // Count positions that meet their brightness requirement
29 let positionsMeetingRequirement: number = 0;
30 let currentBrightness: number = 0;
31
32 // Iterate through each position and calculate actual brightness using prefix sum
33 for (let position: number = 0; position < n; position++) {
34 // Update current brightness by applying the change at this position
35 currentBrightness += brightnessChanges[position];
36
37 // Check if current position meets its brightness requirement
38 if (currentBrightness >= requirement[position]) {
39 positionsMeetingRequirement++;
40 }
41 }
42
43 return positionsMeetingRequirement;
44}
45
Time and Space Complexity
The time complexity is O(m + n)
, where m
is the number of lights and n
is the number of positions. The algorithm iterates through all m
lights to build the difference array (taking O(m)
time), then uses accumulate
to compute prefix sums over n
positions and compares them with requirements (taking O(n)
time). Since the reference answer simplifies this to O(n)
, it likely assumes m ≤ n
in the worst case, making the overall complexity O(n)
.
The space complexity is O(n)
. The algorithm creates a difference array d
of size n + 1
to track the changes in light coverage. The accumulate
function generates values on-the-fly during iteration, and the final sum
operation doesn't require additional space proportional to n
. Therefore, the dominant space usage comes from the difference array, resulting in O(n)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Range Boundaries
The Pitfall: A common mistake is incorrectly handling the range boundaries when marking the difference array. Developers might write:
# INCORRECT - marks wrong end boundary diff_array[right_bound] -= 1 # Wrong!
Why it happens: The difference array technique requires marking the position after the range ends. Since a lamp illuminates positions from left_bound
to right_bound
inclusive, we need to decrement at right_bound + 1
to indicate the light stops affecting positions beyond right_bound
.
Solution: Always mark the decrement at right_bound + 1
:
# CORRECT diff_array[right_bound + 1] -= 1
2. Insufficient Array Size
The Pitfall: Creating a difference array of size n
instead of n + 1
:
# INCORRECT - array too small diff_array = [0] * n # IndexError when right_bound = n-1
Why it happens: When right_bound = n - 1
, we need to access diff_array[n]
to mark the range end. Without the extra element, this causes an IndexError.
Solution: Always allocate n + 1
elements:
# CORRECT diff_array = [0] * (n + 1)
3. Misunderstanding Accumulate Output Length
The Pitfall: Assuming accumulate(diff_array)
returns exactly n
elements when the input has n + 1
elements.
Why it happens: accumulate
returns an iterator with the same number of elements as the input. Since diff_array
has n + 1
elements, accumulate(diff_array)
also yields n + 1
values, but we only need the first n
for comparison with requirement
.
Solution: The zip
function naturally handles this by stopping at the shorter sequence (requirement has n
elements), so the code works correctly. However, for clarity, you could explicitly slice:
# More explicit version
brightness_values = list(accumulate(diff_array))[:n]
positions_meeting_requirement = sum(
brightness >= required
for brightness, required in zip(brightness_values, requirement)
)
4. Integer Overflow in Other Languages
The Pitfall: While not an issue in Python, implementing this in languages like C++ or Java requires careful consideration of integer overflow when many lamps overlap at the same position.
Solution: In Python, integers have arbitrary precision, so this isn't a concern. In other languages, use appropriate data types (e.g., long
in Java, long long
in C++) if the number of overlapping lamps could be large.
How does merge sort divide the problem into subproblems?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!