Reschedule Meetings for Maximum Free Time II

Medium
12 days ago

You are given an integer eventTime denoting the duration of an event. You are also given two integer arrays startTime and endTime, each of length n. These represent the start and end times of n non-overlapping meetings that occur during the event between time t = 0 and time t = eventTime, where the i<sup>th</sup> meeting occurs during the time [startTime[i], endTime[i]].

You can reschedule at most one meeting by moving its start time while maintaining the same duration, such that the meetings remain non-overlapping, to maximize the longest continuous period of free time during the event.

Return the maximum amount of free time possible after rearranging the meetings.

Note that the meetings can not be rescheduled to a time outside the event and they should remain non-overlapping.

Note: In this version, it is valid for the relative ordering of the meetings to change after rescheduling one meeting.

Example 1: eventTime = 5, startTime = [1,3], endTime = [2,5] Output: 2 Explanation: Reschedule the meeting at [1, 2] to [2, 3], leaving no meetings during the time [0, 2].

Example 2: eventTime = 10, startTime = [0,7,9], endTime = [1,8,10] Output: 7 Explanation: Reschedule the meeting at [0, 1] to [8, 9], leaving no meetings during the time [0, 7].

What is the most efficient way to implement this function?

Sample Answer
## Naive Approach

The simplest approach is to iterate through all possible meetings, and for each meeting, try to reschedule it to every possible valid time slot. For each rescheduling, calculate the maximum free time and keep track of the maximum.

1.  **Iterate through Meetings:** For each meeting, consider it for rescheduling.
2.  **Iterate through Possible Time Slots:** For each meeting, iterate through all possible start times to which it can be moved.
3.  **Check Validity:** Ensure the new time slot doesn't overlap with other meetings and stays within the event time.
4.  **Calculate Free Time:** Calculate the longest continuous free time for each valid rescheduling.
5.  **Update Maximum:** Keep track of the maximum free time found so far.

```python
def max_free_time_naive(eventTime, startTime, endTime):
    n = len(startTime)
    max_free = 0

    for i in range(n):
        original_start = startTime[i]
        original_end = endTime[i]
        duration = original_end - original_start

        # Remove the current meeting from the schedule temporarily
        temp_start_times = startTime[:i] + startTime[i+1:]
        temp_end_times = endTime[:i] + endTime[i+1:]

        for new_start in range(eventTime - duration + 1):
            new_end = new_start + duration
            
            # Check for overlaps with other meetings
            overlap = False
            for j in range(n - 1):
                if not (new_end <= temp_start_times[j] or new_start >= temp_end_times[j]):
                    overlap = True
                    break
            
            if not overlap:
                # Calculate free time with the new schedule
                combined_start_times = temp_start_times + [new_start]
                combined_end_times = temp_end_times + [new_end]
                
                # Sort by start time
                meetings = sorted(zip(combined_start_times, combined_end_times))
                
                max_current_free = 0
                current_free = meetings[0][0]
                max_current_free = max(max_current_free, current_free)

                for k in range(n - 1):
                    current_free = meetings[k+1][0] - meetings[k][1]
                    max_current_free = max(max_current_free, current_free)

                current_free = eventTime - meetings[-1][1]
                max_current_free = max(max_current_free, current_free)

                max_free = max(max_free, max_current_free)

    # Also consider the case where no rescheduling happens
    meetings = sorted(zip(startTime, endTime))
    max_current_free = 0
    current_free = meetings[0][0]
    max_current_free = max(max_current_free, current_free)

    for k in range(n - 1):
        current_free = meetings[k+1][0] - meetings[k][1]
        max_current_free = max(max_current_free, current_free)

    current_free = eventTime - meetings[-1][1]
    max_current_free = max(max_current_free, current_free)

    max_free = max(max_free, max_current_free)

    return max_free

Optimal Approach

The optimal approach involves sorting the meetings by their start times, identifying gaps, and then trying to maximize these gaps by rescheduling one meeting.

  1. Sort Meetings: Sort the meetings by their start times.
  2. Calculate Initial Gaps: Calculate the gaps between consecutive meetings and the gaps at the beginning and end of the event.
  3. Identify Potential Reschedule: For each meeting, determine the maximum increase in free time by rescheduling it.
  4. Reschedule and Calculate: Reschedule the meeting to a position that maximizes the longest free time without overlapping.
  5. Update Maximum Free Time: Keep track of the maximum free time found.
def max_free_time_optimal(eventTime, startTime, endTime):
    meetings = sorted(zip(startTime, endTime))
    n = len(meetings)
    max_free = 0

    # Calculate initial gaps
    gaps = []
    gaps.append(meetings[0][0])  # Gap at the beginning
    for i in range(n - 1):
        gaps.append(meetings[i+1][0] - meetings[i][1])
    gaps.append(eventTime - meetings[-1][1])  # Gap at the end

    # Calculate initial max free time
    max_free = max(gaps)

    # Try rescheduling each meeting
    for i in range(n):
        original_start = meetings[i][0]
        original_end = meetings[i][1]
        duration = original_end - original_start

        # Try moving the meeting to fill each gap
        for j in range(len(gaps)):
            # Calculate potential new start time
            if j == 0:
                new_start = 0
            elif j == len(gaps) - 1:
                new_start = eventTime - duration
            else:
                new_start = meetings[j][0] # This is wrong, should be using the previous end time
                new_start = meetings[j-1][1]
            
            new_end = new_start + duration
            
            if new_start < 0:
                continue
            if new_end > eventTime:
                continue
            
            # Check for overlap with other meetings
            overlap = False
            for k in range(n):
                if i == k:
                    continue
                if not (new_end <= meetings[k][0] or new_start >= meetings[k][1]):
                    overlap = True
                    break
                    
            if not overlap:

                temp_meetings = []
                for k in range(n):
                    if i != k:
                        temp_meetings.append(meetings[k])
                temp_meetings.append((new_start, new_end))
                temp_meetings = sorted(temp_meetings)

                new_gaps = []
                new_gaps.append(temp_meetings[0][0])  # Gap at the beginning
                for k in range(len(temp_meetings) - 1):
                    new_gaps.append(temp_meetings[k+1][0] - temp_meetings[k][1])
                new_gaps.append(eventTime - temp_meetings[-1][1])  # Gap at the end
                
                max_free = max(max_free, max(new_gaps))

    return max_free

Edge Cases

  1. No Meetings: If there are no meetings, the entire event time is free.
  2. Meetings Cover Entire Event: If the meetings cover the entire event time with no gaps, the maximum free time is 0.
  3. Single Meeting: If there is only one meeting, the free time is the sum of the time before and after the meeting.
  4. Meetings Already Optimized: The initial schedule may already be optimized, and rescheduling any meeting might not improve the maximum free time.
  5. Large Number of Meetings: The number of meetings can be large, so an efficient algorithm is crucial.
  6. Meetings with Zero Duration: While the prompt states that startTime[i] < endTime[i], it might be useful to guard against meetings having zero duration to prevent potential division by zero errors.

Big(O) Time Complexity Analysis

Optimal Approach:

  1. Sorting: O(n log n), where n is the number of meetings.

  2. Initial Gaps Calculation: O(n)

  3. Rescheduling Loop:

    • Outer loop iterates through each meeting: O(n)
    • Inner loop iterates through each gap: O(n)
    • Overlap check: O(n)
    • Gap calculation: O(n)

    So, the rescheduling loop is O(n * n * n) = O(n^3)

  4. Overall: O(n log n) + O(n) + O(n^3) which simplifies to O(n^3)

Big(O) Space Complexity Analysis

Optimal Approach:

  1. Meetings Array: O(n)
  2. Gaps Array: O(n)
  3. Temp Meetings Array: O(n)
  4. Other variables: O(1)

So, the overall space complexity is O(n).