Taro Logo

Amount of New Area Painted Each Day

#1716 Most AskedHard
12 views
Topics:
ArraysGreedy Algorithms

You are given a 2D integer array paint of size n x 2 representing the coordinates of the areas that need to be painted on a canvas. Each paint[i] = [starti, endi] represents the half-closed interval [starti, endi).

You need to calculate the amount of fresh area painted each day. Fresh area means the area of a region that was not previously painted.

Return an integer array fresh of size n where fresh[i] represents the fresh area on the ith day.

Example 1:

Input: paint = [[1,4],[4,7],[5,8]]
Output: [3,3,1]
Explanation:
Day 1: The canvas was not previously painted, so the fresh area is [1,4) = 3
Day 2: The canvas was not previously painted, so the fresh area is [4,7) = 3
Day 3: The canvas was previously painted partly. The fresh area  is [7,8) = 1

Example 2:

Input: paint = [[1,5],[2,4]]
Output: [4,0]
Explanation:
Day 1: The canvas was not previously painted, so the fresh area is [1,5) = 4
Day 2: The range [2,4) is already painted, so the fresh area is 0

Example 3:

Input: paint = [[1,4],[5,8],[4,7]]
Output: [3,3,3]

Constraints:

  • 1 <= paint.length <= 105
  • paint[i].length == 2
  • 0 <= paint[i][0] < paint[i][1] <= 105

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the possible values for the start and end of each interval in the input array? Are they guaranteed to be non-negative integers?
  2. Could the input array be empty, or could any of the individual intervals be invalid (e.g., start greater than end)? What should I return in those cases?
  3. If intervals overlap on a given day, is the overlapping area counted only once, or should it be counted multiple times?
  4. Is the input array sorted in any particular order, such as by the start time of the intervals? If not, how should I handle unsorted input for optimal performance?
  5. If no new area is painted on a particular day, what should be the output for that day: 0, or should I omit that day entirely from the output?

Brute Force Solution

Approach

We're trying to figure out the new area painted each day given a series of painting jobs. The brute force method involves checking every possible combination of intervals to determine the total painted area at each specific day. We'll essentially simulate the painting process day by day.

Here's how the algorithm would work step-by-step:

  1. Consider each day individually, starting from the first day any painting job begins.
  2. For a given day, go through all painting jobs to see which ones are active (meaning they started before or on that day and end on or after that day).
  3. For each active job, determine the portion of the wall that's being painted during that particular day by figuring out the overlap between the job's interval and the current day.
  4. Sum up the lengths of all the wall portions being painted on that day to get the total area painted on that day.
  5. Repeat this process for every day within the range of painting job start and end dates.
  6. Once we know the total area painted each day, determine the new area painted on a given day by subtracting the area painted on the previous day (or zero if it's the first day with any painting).
  7. Keep track of these new painted areas for each day, giving us the final answer.

Code Implementation

def amount_of_new_area_painted_each_day_brute_force(paintings):
    start_days = [painting[0] for painting in paintings]
    end_days = [painting[1] for painting in paintings]

    first_day = min(start_days)
    last_day = max(end_days)

    total_area_painted_per_day = []

    for current_day in range(first_day, last_day + 1):
        area_painted_today = 0

        for start, end in paintings:
            # Check if current painting job is active today
            if start <= current_day <= end:
                area_painted_today += 1

        total_area_painted_per_day.append(area_painted_today)

    new_area_painted_per_day = []

    for i in range(len(total_area_painted_per_day)):
        if i == 0:
            # Handle the first day by calculating the area
            new_area_painted_per_day.append(total_area_painted_per_day[i])

        else:
            # Get the area painted during that day by doing subtraction
            daily_area = total_area_painted_per_day[i]
            yesterday_area = total_area_painted_per_day[i - 1]

            new_area_painted_per_day.append(daily_area - yesterday_area)

    return new_area_painted_per_day

Big(O) Analysis

Time Complexity
O(n*m) – The algorithm iterates through each day within the range of painting jobs, where n represents the total number of days spanning the earliest start to the latest end date. For each day, it iterates through all painting jobs, where m represents the number of painting jobs. Inside the inner loop, it checks for overlaps between the job's interval and the current day. Therefore, the time complexity is proportional to the product of the number of days and the number of painting jobs, resulting in O(n*m).
Space Complexity
O(1) – The described brute force solution primarily iterates through the painting jobs and calculates areas day by day without using any auxiliary data structures that scale with the input size. It uses a constant number of variables to store intermediate calculations like the current day, the area painted on that day, and the area painted on the previous day. The number of painting jobs (which would be equivalent to N, if we consider the number of jobs as the input size) does not affect the auxiliary space required. Thus, the space complexity is constant.

Optimal Solution

Approach

The problem describes painting segments on a line, and figuring out how much new area gets painted each day. The key is to keep track of what's already been painted and only count the newly painted area on a given day. We can do this efficiently by merging overlapping intervals as we go.

Here's how the algorithm would work step-by-step:

  1. Think of the painted areas on a line as intervals or segments.
  2. Start with an empty line (nothing painted).
  3. For each day, you are given a new segment to paint.
  4. Check if this new segment overlaps with any existing painted segments.
  5. If it doesn't overlap, simply add the length of the new segment to the total new area painted that day.
  6. If it does overlap, merge the new segment with the overlapping segments to create a single, combined segment.
  7. Calculate the length of the combined segment minus the length of the previously painted segments that were part of this combined segment. This gives you the newly painted area for the day.
  8. Update the list of painted segments with the merged segment.
  9. Repeat this process for each day, tracking the total new area painted each day.

Code Implementation

def amount_of_new_area_painted(paintings):
    painted_intervals = []
    total_new_area = 0

    for painting in paintings:
        start, end = painting
        new_area_this_day = 0
        intervals_to_merge = [painting]
        new_painted_intervals = []

        for existing_start, existing_end in painted_intervals:
            # Check for overlap. Merge if overlapping.
            if max(start, existing_start) < min(end, existing_end):
                intervals_to_merge.append((existing_start, existing_end))
            else:
                new_painted_intervals.append((existing_start, existing_end))

        # Sort intervals for easy merging
        intervals_to_merge.sort()

        merged_start = intervals_to_merge[0][0]
        merged_end = intervals_to_merge[0][1]

        for i in range(1, len(intervals_to_merge)):
            current_start, current_end = intervals_to_merge[i]
            if current_start <= merged_end:
                merged_end = max(merged_end, current_end)
            else:
                new_painted_intervals.append((merged_start, merged_end))
                merged_start = current_start
                merged_end = current_end
        new_painted_intervals.append((merged_start, merged_end))

        # Calculate the total area after merging
        total_area_after_merge = 0
        for start_interval, end_interval in new_painted_intervals:
            total_area_after_merge += (end_interval - start_interval)

        # Calculate area of intervals to merge before merging.
        total_area_before_merge = 0
        for start_interval, end_interval in intervals_to_merge:
            total_area_before_merge += (end_interval - start_interval)

        # Need the last interval added to main list.
        new_area_this_day = total_area_after_merge - (total_area_before_merge - (merged_end - merged_start))
        total_new_area += new_area_this_day

        painted_intervals = new_painted_intervals

    return total_new_area

Big(O) Analysis

Time Complexity
O(n²) – The algorithm iterates through each day, which is the main loop running 'n' times where 'n' is the number of days. Within each day's iteration, it checks for overlaps with existing painted segments. In the worst-case scenario, each new segment might need to be compared with all previously painted segments to check for overlaps and potentially merge them, leading to a nested loop-like behavior. Thus, the overall time complexity arises from this comparison within the loop for each of 'n' days which approximates to n * n/2 comparisons. Therefore, the time complexity simplifies to O(n²).
Space Complexity
O(N) – The primary auxiliary space is used to store the list of painted segments. In the worst-case scenario, where no segments overlap, we would store all N segments provided as input. Therefore, the space required grows linearly with the number of input segments, N, leading to a space complexity of O(N).

Edge Cases

Empty intervals array
How to Handle:
Return 0 immediately as no area is painted.
Null or invalid intervals array
How to Handle:
Throw an IllegalArgumentException for invalid input.
Intervals with invalid start/end (start > end)
How to Handle:
Swap the start and end values to ensure the interval is valid.
Very large intervals that could cause integer overflow when calculating painted area
How to Handle:
Use long data type for calculations to prevent overflow during area computation.
Overlapping intervals on the same day
How to Handle:
Merge overlapping intervals within each day to avoid double-counting painted area.
Non-positive interval lengths (start == end)
How to Handle:
Ignore such intervals, as they contribute nothing to the painted area.
Large number of intervals, potentially causing performance issues
How to Handle:
Use an efficient sorting algorithm (e.g., merge sort) when merging intervals to optimize performance.
Negative interval coordinates
How to Handle:
Shift all intervals to positive range to handle negative coordinates.
0/1916 completed