2237. Count Positions on Street With Required Brightness


Problem Description

You are provided with an integer n which represents the length of a street on a number line from 0 to n - 1. There are street lamps along this street, given by a 2D integer array named lights. Each element of lights is a pair [position_i, range_i], where position_i denotes the location of a street lamp and range_i is the range of its light. This range means the lamp can illuminate the street from [max(0, position_i - range_i), min(n - 1, position_i + range_i)] (both ends included).

The brightness at any position p on the street is defined as the count of street lamps that cover the position p. A separate 0-indexed integer array named requirement specifies the minimum brightness required at each position i of the street.

The goal is to find and return the count of positions i on the street (between 0 to n - 1) where the brightness is at least the specified requirement at i.

Intuition

The intuition of the solution involves understanding that we need to calculate the brightness at each position on the street and then check if it meets the requirement for that position. A key observation here is that by turning a lamp on, it increases the brightness of all positions within its range; similarly, turning it off would decrease the brightness. This is similar to an interval update in a range query problem.

The challenge then is how to efficiently calculate the brightness at all positions, given that each lamp can affect a potentially broad range and there can be multiple overlaps from different lamps. Directly updating each range for each lamp would lead to a solution that is too slow, as each lamp could affect up to n positions.

To solve this efficiently, one can use a technique known as prefix sum or cumulative sum. We can increment the brightness at the starting position of a lamp's range and decrement it just after the ending position of the lamp's range. By doing this for all lamps, we would have an array where the value at each position indicates how much the total brightness changes at that point on the street. Accumulating these changes from the beginning to the end will give us the actual brightness at each position.

Once we have the brightness at each position, it's straightforward to compare it with the requirement at each position and count the positions where the brightness is adequate.

The given solution code performs exactly this:

  1. Initializes an array d to store the differences in brightness.
  2. Loops through each lamp and updates the d array to include the brightness change at the start and just after the end of each lamp's range.
  3. Utilizes Python's accumulate function to calculate the prefix sum of brightness changes, giving the actual brightness at each position.
  4. Counts the number of positions where the actual brightness meets or exceeds the required brightness.

The solution is elegant and efficient, taking advantage of subtle changes in brightness rather than brute-force updating the entire range for every lamp, resulting in a more time-efficient approach.

Learn more about Prefix Sum patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.

Solution Approach

The solution uses a difference array to efficiently manage brightness updates across the range that each lamp covers. Here's a walkthrough of the steps involved in the solution:

  1. Initialize a difference array d, which is an auxiliary array that allows us to apply updates to the original array in constant time. In our case, the difference array is oversized to avoid boundary checks later.

  2. Loop through each lamp's properties given in the lights array. For every lamp with parameters [position_i, range_i], calculate the starting index i and ending index j for its illumination. This is done by ensuring that the range does not exceed the boundaries of the street, i.e., between 0 and n - 1.

  3. Update the difference array d by incrementing the value at index i by 1 and decrementing the value at index j + 1 by 1. The increment at index i signifies that from this point onwards, the brightness level has increased due to the lamp, while the decrement at j + 1 marks that beyond this point, the influence of the current lamp does not extend, effectively lowering the brightness level.

  4. Now that we have set up the difference array, we use the accumulate function in Python to compute the prefix sum, which essentially applies all the increments and decrements we've added in the difference array and yields the actual brightness level at each index. The accumulate function will perform this operation in O(n) time across the difference array, thereby generating the brightness levels for the entire street.

  5. Finally, we loop over the prefix sum result alongside the requirement array, comparing values at each position. For every position, we check if the brightness level at that index (obtained from the prefix sum) is greater than or equal to the required brightness level. We count all such occurrences where the condition is satisfied.

The overall time complexity is O(n + m), where n is the length of the street and m is the number of lamps. This is because we process each lamp to update the difference array once and then scan through the array of length n to compute the prefix sum and compare it against requirements.

In summary, the code efficiently uses a difference array technique and the power of prefix sum to obtain brightness levels across the street, followed by a simple tally against the specified brightness requirements.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Given an array of 1,000,000 integers that is almost sorted, except for 2 pairs of integers. Which algorithm is fastest for sorting the array?

Example Walkthrough

Let's consider an example to illustrate the solution approach.

Suppose we are given:

  • A street of length n = 5, which is from position 0 to 4.
  • An array lights = [[1, 2], [3, 1]], indicating there are two street lamps. The first lamp is at position 1 with a range of 2 and can illuminate from position 0 to 3. The second lamp is at position 3 with a range of 1 and can illuminate from position 2 to 4.
  • An array requirement = [1, 2, 1, 1, 2] specifying the minimum brightness required at each position on the street.

We want to find the count of positions where the brightness is at least the specified requirement.

Let's proceed with the steps:

  1. Initialize a difference array d of size n + 1 to handle brightness changes, thus d = [0, 0, 0, 0, 0, 0].

  2. Process the first lamp [1, 2]. The starting index i is max(0, 1 - 2) = 0 and the ending index j is min(4, 1 + 2) = 3. Update d by incrementing d[i] and decrementing d[j + 1], leading to d = [1, 0, 0, 0, -1, 0].

  3. Process the second lamp [3, 1]. The starting index i is max(0, 3 - 1) = 2 and the ending index j is min(4, 3 + 1) = 4. Update d by incrementing d[i] and decrementing d[j + 1], which after the update gives d = [1, 0, 1, 0, -1, -1].

  4. Compute the prefix sum with the accumulate function to determine the actual brightness at each position. After using accumulate, the brightness levels array becomes [1, 1, 2, 2, 1, 0].

  5. Compare these brightness levels with requirement. At positions 0, 1, 2, and 3, the brightness equals or exceeds the requirement, which gives us 4 positions meeting the requirement. Position 4 has a brightness of 1, which does not meet the requirement of 2.

Hence, the final answer is 4 positions with adequate brightness.

Solution Implementation

1from itertools import accumulate
2from typing import List
3
4class Solution:
5    def meetRequirement(self, n: int, lights: List[List[int]], requirement: List[int]) -> int:
6        # Create a delta array to keep track of the light increments and decrements
7        delta = [0] * (n + 1)
8      
9        # Loop through each light's position and range to update the delta array
10        for position, range in lights:
11            # Calculate the left and right effective indices
12            left_effective_index = max(0, position - range)
13            right_effective_index = min(n - 1, position + range)
14          
15            # Increase the light intensity at the left index
16            delta[left_effective_index] += 1
17            # Decrease the light intensity right after the right index
18            delta[right_effective_index + 1] -= 1
19      
20        # Use accumulate to get the prefix sum of the delta array
21        # which represents the actual lighting at each position
22        actual_lighting = list(accumulate(delta))[:-1]
23      
24        # Determine the count of positions meeting the lighting requirement by comparing
25        # actual lighting against the requirement and summing where the condition is true
26        return sum(actual_lighting[i] >= requirement[i] for i in range(n))
27
1class Solution {
2
3    // Method to calculate the number of positions that meet the required brightness
4    public int meetRequirement(int n, int[][] lights, int[] requirement) {
5        // Array 'brightnessChanges' holds the net change in brightness at each position
6        int[] brightnessChanges = new int[100010];
7
8        // Loop through the array of lights to populate the 'brightnessChanges' array
9        for (int[] light : lights) {
10            // Calculate the effective range of light for each light bulb
11            // Make sure the range does not go below 0 or above n-1
12            int start = Math.max(0, light[0] - light[1]);
13            int end = Math.min(n - 1, light[0] + light[1]);
14          
15            // Increment brightness at the start position
16            ++brightnessChanges[start];
17            // Decrement brightness just after the end position
18            --brightnessChanges[end + 1];
19        }
20
21        int currentBrightness = 0; // Holds the cumulative brightness at each position
22        int positionsMeetingReq = 0; // Number of positions meeting the requirement
23
24        // Iterate over positions from 0 to n-1
25        for (int i = 0; i < n; ++i) {
26            // Calculate the current brightness by adding the net brightness change at position i
27            currentBrightness += brightnessChanges[i];
28
29            // If current brightness meets or exceeds the requirement at position i, increase count
30            if (currentBrightness >= requirement[i]) {
31                ++positionsMeetingReq;
32            }
33        }
34
35        // Return the total number of positions meeting the brightness requirement
36        return positionsMeetingReq;
37    }
38}
39
1class Solution {
2public:
3    int meetRequirement(int n, vector<vector<int>>& lights, vector<int>& requirement) {
4        // Define a vector to keep track of the differential lighting reach at positions.
5        vector<int> lightingDiff(100010, 0);
6
7        // Iterate through each light source to create the differential array.
8        for (auto& light : lights) {
9            // Calculate the effective range of this light source.
10            int leftBound = max(0, light[0] - light[1]);
11            int rightBound = min(n - 1, light[0] + light[1]);
12
13            // Increment the start of the range.
14            ++lightingDiff[leftBound];
15            // Decrement the position just after the end of the range.
16            --lightingDiff[rightBound + 1];
17        }
18
19        // 'brightnessAccum' will keep track of accumulated brightness as we move along the positions.
20        int brightnessAccum = 0;
21        // 'satisfyingPositions' is the count of positions meeting the requirement.
22        int satisfyingPositions = 0;
23
24        // Iterate through each position and sum up the differential to get the actual brightness.
25        for (int i = 0; i < n; ++i) {
26            brightnessAccum += lightingDiff[i];
27            // If current accumulated brightness meets or exceeds the requirement, increment the count.
28            if (brightnessAccum >= requirement[i]) {
29                ++satisfyingPositions;
30            }
31        }
32
33        // Return the number of positions where lighting requirements are met.
34        return satisfyingPositions;
35    }
36};
37
1// Define a global constant for the maximum number of positions
2const MAX_POSITIONS = 100010;
3
4// 'lightingDiff' is an array to keep track of the differential lighting reach at positions.
5let lightingDiff: number[] = new Array(MAX_POSITIONS).fill(0);
6
7/**
8 * The function 'meetRequirement' calculates the number of positions where the lighting requirements are met.
9 * @param {number} n - Number of positions.
10 * @param {number[][]} lights - Array of light sources, each with position and range.
11 * @param {number[]} requirement - Array of lighting requirements for each position.
12 * @returns {number} - The number of positions that meet the lighting requirements.
13 */
14function meetRequirement(n: number, lights: number[][], requirement: number[]): number {
15    // Iterate through each light source to create the differential array.
16    for (let light of lights) {
17        // Calculate the effective range of this light source.
18        let leftBound = Math.max(0, light[0] - light[1]);
19        let rightBound = Math.min(n - 1, light[0] + light[1]);
20
21        // Increment the start of the range.
22        lightingDiff[leftBound]++;
23        // Decrement the position just after the end of the range.
24        lightingDiff[rightBound + 1]--;
25    }
26
27    // 'brightnessAccum' will keep track of accumulated brightness as we move along the positions.
28    let brightnessAccum = 0;
29    // 'satisfyingPositions' is the count of positions meeting the requirement.
30    let satisfyingPositions = 0;
31
32    // Iterate through each position and sum up the differential to get the actual brightness.
33    for (let i = 0; i < n; i++) {
34        brightnessAccum += lightingDiff[i];
35        // If current accumulated brightness meets or exceeds the requirement, increment the count.
36        if (brightnessAccum >= requirement[i]) {
37            satisfyingPositions++;
38        }
39    }
40
41    // Reset 'lightingDiff' for potential subsequent calls to 'meetRequirement' to ensure correctness
42    lightingDiff.fill(0);
43
44    // Return the number of positions where lighting requirements are met.
45    return satisfyingPositions;
46}
47
Not Sure What to Study? Take the 2-min Quiz:

Which of the following problems can be solved with backtracking (select multiple)

Time and Space Complexity

Time Complexity

The main operations within the meetRequirement function are as follows:

  1. Iterating over the lights list to populate the differences in the d array. This has a time complexity of O(m), where m is the length of the lights list.
  2. Using the itertools.accumulate function to compute the prefix sums of the array d. This has a time complexity of O(n) since accumulate will sum across the n elements of the d array.
  3. Zipping the accumulated sums with the requirement array and iterating over it to count the number of positions meeting the requirement. The zipping has a time complexity of O(n) since it operates on two arrays of n elements each. The sum operation also takes O(n).

Therefore, the time complexity of the complete function is O(m + n) because O(m) for the iterations over the lights list and O(n) for the operations involving the d array are independent and do not nest.

Space Complexity

For space complexity, the main data structures that are used in the function include:

  1. The difference array d, which has a fixed maximum size due to its initialization. This maximum size (100010) gives us a space complexity of O(1) since it does not scale with the input size n.
  2. The use of itertools.accumulate which generates an iterator. The space taken by this iterator is proportional to the number of elements in d, leading to a space complexity of O(n).
  3. The intermediate tuples created during the zipping process are not stored; they're generated on-the-fly during iteration, making their additional space impact negligible.

In conclusion, the space complexity of the function is O(n) due to the storage requirements of the difference array d as it scales linearly with the input size n.

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫