3440. Reschedule Meetings for Maximum Free Time II
Problem Description
You have an event that runs from time t = 0
to time t = eventTime
. During this event, there are n
non-overlapping meetings scheduled. Each meeting i
has a start time startTime[i]
and end time endTime[i]
.
Your task is to find the maximum possible continuous free time period during the event. You can achieve this by rescheduling at most one meeting. When rescheduling a meeting:
- You can change its start time to any valid position
- The meeting must keep the same duration (end time - start time remains unchanged)
- The rescheduled meeting cannot overlap with any other meetings
- The rescheduled meeting must still occur within the event time bounds
[0, eventTime]
The goal is to maximize the longest continuous period where no meetings are scheduled.
For example, if you have meetings at [1,3]
and [5,7]
during an event from 0
to 10
, there are three free periods: [0,1]
, [3,5]
, and [7,10]
. The longest free period is 3 units (from 7 to 10). However, if you reschedule the second meeting to [7,9]
, you create a longer free period [3,7]
of 4 units.
Return the maximum length of continuous free time you can achieve after optimally rescheduling at most one meeting.
Key Points:
- All meetings are initially non-overlapping
- You can reschedule at most one meeting (or none)
- The rescheduled meeting must maintain its original duration
- After rescheduling, meetings can change their relative order (unlike some variants of this problem)
- The rescheduled meeting must remain within the event bounds and not overlap with others
Intuition
To maximize the free time, we need to think about what happens when we move a meeting. Each meeting creates a "barrier" in the timeline, and moving one meeting can potentially merge two separate free periods into one larger continuous free period.
Let's think about meeting i
with duration w_i = endTime[i] - startTime[i]
. This meeting sits between two boundaries:
- Left boundary
l_i
: the end of the previous meeting (or 0 if it's the first meeting) - Right boundary
r_i
: the start of the next meeting (or eventTime if it's the last meeting)
Without moving any meeting, the free space around meeting i
is r_i - l_i - w_i
.
Now, what if we move meeting i
? The key insight is that we want to "hide" this meeting somewhere else to create a larger free space. Where can we hide it?
-
Move it to an existing free slot on the left: If there's a free period earlier in the timeline that's at least as large as
w_i
, we can place meetingi
there. This would create a new continuous free period of sizer_i - l_i
. -
Move it to an existing free slot on the right: Similarly, if there's a free period later that can accommodate meeting
i
, we get the same benefit: a free period of sizer_i - l_i
.
This leads us to precompute two important pieces of information:
pre[i]
: the maximum free slot size from the beginning up to positioni
suf[i]
: the maximum free slot size from positioni
to the end
For each meeting, we check three scenarios:
- Don't move it: free time =
r_i - l_i - w_i
- Move it left (if
pre[i-1] >= w_i
): free time =r_i - l_i
- Move it right (if
suf[i+1] >= w_i
): free time =r_i - l_i
The answer is the maximum free time across all meetings and all valid scenarios. This greedy approach works because we're looking for the single best move that creates the longest continuous free period.
Learn more about Greedy patterns.
Solution Approach
Based on our intuition, we implement the solution using dynamic programming to precompute the maximum free slots, then iterate through each meeting to find the optimal rescheduling.
Step 1: Precompute Maximum Free Slots
First, we build two arrays:
pre[i]
: stores the maximum free slot size from the start up to and including positioni
suf[i]
: stores the maximum free slot size from positioni
to the end
pre[0] = startTime[0] # Free time before first meeting suf[n-1] = eventTime - endTime[-1] # Free time after last meeting
For pre
, we iterate forward:
for i in range(1, n):
pre[i] = max(pre[i-1], startTime[i] - endTime[i-1])
This takes the maximum of the previous maximum and the current gap between meetings.
For suf
, we iterate backward:
for i in range(n-2, -1, -1):
suf[i] = max(suf[i+1], startTime[i+1] - endTime[i])
Step 2: Evaluate Each Meeting
For each meeting i
, we determine:
- Left boundary
l
:l = 0
ifi = 0
(first meeting)l = endTime[i-1]
otherwise
- Right boundary
r
:r = eventTime
ifi = n-1
(last meeting)r = startTime[i+1]
otherwise
- Meeting duration:
w = endTime[i] - startTime[i]
Step 3: Calculate Maximum Free Time
For each meeting, we consider three scenarios:
-
Don't move the meeting: The free space created is
r - l - w
-
Move to a left slot: If
i > 0
andpre[i-1] >= w
, the meeting can fit in a previous free slot, creating a continuous free period ofr - l
-
Move to a right slot: If
i < n-1
andsuf[i+1] >= w
, the meeting can fit in a later free slot, also creating a free period ofr - l
The algorithm tracks the maximum free time across all meetings and scenarios:
ans = max(ans, r - l - w) # Don't move
if i > 0 and pre[i-1] >= w:
ans = max(ans, r - l) # Move left
if i+1 < n and suf[i+1] >= w:
ans = max(ans, r - l) # Move right
Time Complexity: O(n)
where n
is the number of meetings. We make three passes through the array.
Space Complexity: O(n)
for storing the pre
and suf
arrays.
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.
Example Input:
eventTime = 10
startTime = [1, 4, 7]
endTime = [2, 6, 9]
This gives us meetings at [1,2]
, [4,6]
, and [7,9]
during an event from time 0 to 10.
Step 1: Build the pre and suf arrays
First, let's identify all free slots:
- Free slot before meeting 0:
[0,1]
with size 1 - Free slot between meetings 0 and 1:
[2,4]
with size 2 - Free slot between meetings 1 and 2:
[6,7]
with size 1 - Free slot after meeting 2:
[9,10]
with size 1
Now we build pre
(maximum free slot from start up to position i):
pre[0] = 1
(free slot[0,1]
before first meeting)pre[1] = max(1, 2) = 2
(comparing previous max with gap[2,4]
)pre[2] = max(2, 1) = 2
(comparing previous max with gap[6,7]
)
And suf
(maximum free slot from position i to end):
suf[2] = 1
(free slot[9,10]
after last meeting)suf[1] = max(1, 1) = 1
(comparing next max with gap[6,7]
)suf[0] = max(1, 2) = 2
(comparing next max with gap[2,4]
)
Step 2: Evaluate each meeting for rescheduling
Meeting 0: [1,2]
- Left boundary
l = 0
, right boundaryr = 4
(start of next meeting) - Duration
w = 2 - 1 = 1
- Option 1 (don't move): free space =
4 - 0 - 1 = 3
- Option 2 (move left): Not applicable (first meeting)
- Option 3 (move right): Check if
suf[1] >= 1
. Sincesuf[1] = 1
, we can move it, creating free space =4 - 0 = 4
- Best for this meeting: 4
Meeting 1: [4,6]
- Left boundary
l = 2
(end of previous), right boundaryr = 7
(start of next) - Duration
w = 6 - 4 = 2
- Option 1 (don't move): free space =
7 - 2 - 2 = 3
- Option 2 (move left): Check if
pre[0] >= 2
. Sincepre[0] = 1 < 2
, cannot move left - Option 3 (move right): Check if
suf[2] >= 2
. Sincesuf[2] = 1 < 2
, cannot move right - Best for this meeting: 3
Meeting 2: [7,9]
- Left boundary
l = 6
(end of previous), right boundaryr = 10
(event end) - Duration
w = 9 - 7 = 2
- Option 1 (don't move): free space =
10 - 6 - 2 = 2
- Option 2 (move left): Check if
pre[1] >= 2
. Sincepre[1] = 2
, we can move it, creating free space =10 - 6 = 4
- Option 3 (move right): Not applicable (last meeting)
- Best for this meeting: 4
Final Answer: 4
The maximum free time is 4, achieved by either:
- Moving meeting 0 to a later position (e.g.,
[6,7]
or[9,10]
), creating free space[0,4]
- Moving meeting 2 to an earlier position (e.g.,
[2,4]
), creating free space[6,10]
This demonstrates how the algorithm efficiently finds the optimal rescheduling by considering each meeting's potential to create larger free periods when moved to existing gaps.
Solution Implementation
1class Solution:
2 def maxFreeTime(
3 self, event_time: int, start_time: List[int], end_time: List[int]
4 ) -> int:
5 n = len(start_time)
6
7 # Arrays to store maximum gaps before and after each position
8 max_gap_before = [0] * n
9 max_gap_after = [0] * n
10
11 # Initialize first and last positions
12 max_gap_before[0] = start_time[0] # Gap from time 0 to first event
13 max_gap_after[n - 1] = event_time - end_time[-1] # Gap from last event to end
14
15 # Calculate maximum gap between consecutive events up to position i
16 for i in range(1, n):
17 # Either previous max gap or gap between current and previous event
18 max_gap_before[i] = max(
19 max_gap_before[i - 1],
20 start_time[i] - end_time[i - 1]
21 )
22
23 # Calculate maximum gap between consecutive events from position i onwards
24 for i in range(n - 2, -1, -1):
25 # Either next max gap or gap between next and current event
26 max_gap_after[i] = max(
27 max_gap_after[i + 1],
28 start_time[i + 1] - end_time[i]
29 )
30
31 max_free_time = 0
32
33 # Try removing each event and calculate resulting free time
34 for i in range(n):
35 # Determine boundaries when event i is removed
36 left_boundary = 0 if i == 0 else end_time[i - 1]
37 right_boundary = event_time if i == n - 1 else start_time[i + 1]
38
39 # Duration of current event
40 event_duration = end_time[i] - start_time[i]
41
42 # Free time if we simply remove event i (creating a gap)
43 max_free_time = max(
44 max_free_time,
45 right_boundary - left_boundary - event_duration
46 )
47
48 # Check if we can reschedule event i to an earlier gap
49 if i > 0 and max_gap_before[i - 1] >= event_duration:
50 # Event fits in a gap before, so entire range becomes free
51 max_free_time = max(max_free_time, right_boundary - left_boundary)
52
53 # Check if we can reschedule event i to a later gap
54 elif i + 1 < n and max_gap_after[i + 1] >= event_duration:
55 # Event fits in a gap after, so entire range becomes free
56 max_free_time = max(max_free_time, right_boundary - left_boundary)
57
58 return max_free_time
59
1class Solution {
2 public int maxFreeTime(int eventTime, int[] startTime, int[] endTime) {
3 int n = startTime.length;
4
5 // pre[i] stores the maximum free time gap from start to position i
6 // (including the gap before event 0 and gaps between consecutive events up to i)
7 int[] maxGapBefore = new int[n];
8
9 // suf[i] stores the maximum free time gap from position i to end
10 // (including gaps between consecutive events from i onwards and the gap after last event)
11 int[] maxGapAfter = new int[n];
12
13 // Initialize: free time before the first event
14 maxGapBefore[0] = startTime[0];
15
16 // Initialize: free time after the last event
17 maxGapAfter[n - 1] = eventTime - endTime[n - 1];
18
19 // Build prefix array: for each position, track maximum gap seen so far
20 for (int i = 1; i < n; i++) {
21 int currentGap = startTime[i] - endTime[i - 1]; // Gap between event i-1 and event i
22 maxGapBefore[i] = Math.max(maxGapBefore[i - 1], currentGap);
23 }
24
25 // Build suffix array: for each position, track maximum gap from this point onwards
26 for (int i = n - 2; i >= 0; i--) {
27 int currentGap = startTime[i + 1] - endTime[i]; // Gap between event i and event i+1
28 maxGapAfter[i] = Math.max(maxGapAfter[i + 1], currentGap);
29 }
30
31 int maxFreeTimeResult = 0;
32
33 // Try removing each event and calculate the resulting free time
34 for (int i = 0; i < n; i++) {
35 // Determine the boundaries after removing event i
36 int leftBoundary = (i == 0) ? 0 : endTime[i - 1];
37 int rightBoundary = (i == n - 1) ? eventTime : startTime[i + 1];
38 int eventDuration = endTime[i] - startTime[i];
39
40 // Free time created by removing event i (merging adjacent gaps)
41 int freeTimeIfRemoved = rightBoundary - leftBoundary - eventDuration;
42 maxFreeTimeResult = Math.max(maxFreeTimeResult, freeTimeIfRemoved);
43
44 // Check if we can relocate event i to an earlier gap
45 if (i > 0 && maxGapBefore[i - 1] >= eventDuration) {
46 // Event can fit in some gap before, so we get the full merged gap
47 maxFreeTimeResult = Math.max(maxFreeTimeResult, rightBoundary - leftBoundary);
48 }
49 // Check if we can relocate event i to a later gap
50 else if (i + 1 < n && maxGapAfter[i + 1] >= eventDuration) {
51 // Event can fit in some gap after, so we get the full merged gap
52 maxFreeTimeResult = Math.max(maxFreeTimeResult, rightBoundary - leftBoundary);
53 }
54 }
55
56 return maxFreeTimeResult;
57 }
58}
59
1class Solution {
2public:
3 int maxFreeTime(int eventTime, vector<int>& startTime, vector<int>& endTime) {
4 int n = startTime.size();
5
6 // maxGapBefore[i] stores the maximum gap between any two consecutive events
7 // from index 0 to i (the gap before or at position i)
8 vector<int> maxGapBefore(n);
9
10 // maxGapAfter[i] stores the maximum gap between any two consecutive events
11 // from index i to n-1 (the gap at or after position i)
12 vector<int> maxGapAfter(n);
13
14 // Initialize the first element: gap before first event
15 maxGapBefore[0] = startTime[0];
16
17 // Initialize the last element: gap after last event
18 maxGapAfter[n - 1] = eventTime - endTime[n - 1];
19
20 // Build prefix maximum array: track maximum gap up to each position
21 for (int i = 1; i < n; ++i) {
22 int currentGap = startTime[i] - endTime[i - 1]; // Gap between event i-1 and event i
23 maxGapBefore[i] = max(maxGapBefore[i - 1], currentGap);
24 }
25
26 // Build suffix maximum array: track maximum gap from each position onwards
27 for (int i = n - 2; i >= 0; --i) {
28 int currentGap = startTime[i + 1] - endTime[i]; // Gap between event i and event i+1
29 maxGapAfter[i] = max(maxGapAfter[i + 1], currentGap);
30 }
31
32 int maxFreeTimeResult = 0;
33
34 // Try skipping each event and calculate the resulting free time
35 for (int i = 0; i < n; ++i) {
36 // Define the boundaries when skipping event i
37 int leftBoundary = (i == 0) ? 0 : endTime[i - 1];
38 int rightBoundary = (i == n - 1) ? eventTime : startTime[i + 1];
39 int eventDuration = endTime[i] - startTime[i];
40
41 // Free time gained by simply removing event i
42 int freeTimeByRemoval = rightBoundary - leftBoundary - eventDuration;
43 maxFreeTimeResult = max(maxFreeTimeResult, freeTimeByRemoval);
44
45 // Check if we can fit the event in an earlier gap
46 if (i > 0 && maxGapBefore[i - 1] >= eventDuration) {
47 // Event can fit in a gap before, so we get the full span as free time
48 maxFreeTimeResult = max(maxFreeTimeResult, rightBoundary - leftBoundary);
49 }
50 // Check if we can fit the event in a later gap
51 else if (i + 1 < n && maxGapAfter[i + 1] >= eventDuration) {
52 // Event can fit in a gap after, so we get the full span as free time
53 maxFreeTimeResult = max(maxFreeTimeResult, rightBoundary - leftBoundary);
54 }
55 }
56
57 return maxFreeTimeResult;
58 }
59};
60
1/**
2 * Finds the maximum free time available by potentially removing one event
3 * @param eventTime - Total duration of the event period
4 * @param startTime - Array of start times for each sub-event
5 * @param endTime - Array of end times for each sub-event
6 * @returns Maximum possible free time
7 */
8function maxFreeTime(eventTime: number, startTime: number[], endTime: number[]): number {
9 const eventCount: number = startTime.length;
10
11 // Array to store maximum gap before or at each position
12 const maxGapBefore: number[] = Array(eventCount).fill(0);
13
14 // Array to store maximum gap after or at each position
15 const maxGapAfter: number[] = Array(eventCount).fill(0);
16
17 // Initialize first element: gap from start to first event
18 maxGapBefore[0] = startTime[0];
19
20 // Initialize last element: gap from last event to end
21 maxGapAfter[eventCount - 1] = eventTime - endTime[eventCount - 1];
22
23 // Build prefix maximum array: maximum gap up to position i
24 for (let i = 1; i < eventCount; i++) {
25 const currentGap: number = startTime[i] - endTime[i - 1];
26 maxGapBefore[i] = Math.max(maxGapBefore[i - 1], currentGap);
27 }
28
29 // Build suffix maximum array: maximum gap from position i onwards
30 for (let i = eventCount - 2; i >= 0; i--) {
31 const currentGap: number = startTime[i + 1] - endTime[i];
32 maxGapAfter[i] = Math.max(maxGapAfter[i + 1], currentGap);
33 }
34
35 let maxFreeTimeResult: number = 0;
36
37 // Try removing each event and calculate resulting free time
38 for (let i = 0; i < eventCount; i++) {
39 // Left boundary of the gap when removing event i
40 const leftBoundary: number = i === 0 ? 0 : endTime[i - 1];
41
42 // Right boundary of the gap when removing event i
43 const rightBoundary: number = i === eventCount - 1 ? eventTime : startTime[i + 1];
44
45 // Width of the current event
46 const eventWidth: number = endTime[i] - startTime[i];
47
48 // Free time if we remove event i (creates a gap from leftBoundary to rightBoundary)
49 const freeTimeWithoutEvent: number = rightBoundary - leftBoundary - eventWidth;
50 maxFreeTimeResult = Math.max(maxFreeTimeResult, freeTimeWithoutEvent);
51
52 // Check if we can fit the event in an earlier gap
53 if (i > 0 && maxGapBefore[i - 1] >= eventWidth) {
54 // Event can fit in a gap before, so we get the full gap when removing it
55 maxFreeTimeResult = Math.max(maxFreeTimeResult, rightBoundary - leftBoundary);
56 }
57 // Check if we can fit the event in a later gap
58 else if (i + 1 < eventCount && maxGapAfter[i + 1] >= eventWidth) {
59 // Event can fit in a gap after, so we get the full gap when removing it
60 maxFreeTimeResult = Math.max(maxFreeTimeResult, rightBoundary - leftBoundary);
61 }
62 }
63
64 return maxFreeTimeResult;
65}
66
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of several sequential operations:
- First loop to build the
pre
array: iterates throughn-1
elements withO(1)
operations per iteration βO(n)
- Second loop to build the
suf
array: iterates throughn-1
elements withO(1)
operations per iteration βO(n)
- Third loop to find the maximum free time: iterates through
n
elements withO(1)
operations per iteration βO(n)
Since these loops run sequentially (not nested), the overall time complexity is O(n) + O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The algorithm uses additional space for:
pre
array of sizen
βO(n)
suf
array of sizen
βO(n)
- A few constant variables (
ans
,l
,r
,w
) βO(1)
The total space complexity is O(n) + O(n) + O(1) = O(n)
, where n
is the number of meetings.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using 'elif' Instead of Independent 'if' Statements for Rescheduling Options
The Pitfall:
In the provided code, there's a critical bug where elif
is used to check if the meeting can be moved to a later gap:
# INCORRECT - using elif
if i > 0 and max_gap_before[i - 1] >= event_duration:
max_free_time = max(max_free_time, right_boundary - left_boundary)
elif i + 1 < n and max_gap_after[i + 1] >= event_duration: # Bug here!
max_free_time = max(max_free_time, right_boundary - left_boundary)
This means if a meeting can be moved to an earlier gap, we never check if it can also be moved to a later gap. Both options should be evaluated independently since we want to consider all possible rescheduling scenarios.
The Solution:
Use independent if
statements to check both possibilities:
# CORRECT - using separate if statements
if i > 0 and max_gap_before[i - 1] >= event_duration:
max_free_time = max(max_free_time, right_boundary - left_boundary)
if i + 1 < n and max_gap_after[i + 1] >= event_duration:
max_free_time = max(max_free_time, right_boundary - left_boundary)
2. Forgetting to Consider Not Moving Any Meeting
The Pitfall: Another common mistake is forgetting to calculate the baseline maximum free time when no meetings are rescheduled. Some implementations might only look at rescheduling scenarios.
The Solution: Always initialize by finding the maximum free gap in the original schedule:
# Don't forget the base case - find max gap without moving anything
max_free_time = start_time[0] # Gap before first meeting
for i in range(1, n):
max_free_time = max(max_free_time, start_time[i] - end_time[i-1])
max_free_time = max(max_free_time, event_time - end_time[-1]) # Gap after last meeting
3. Incorrect Boundary Handling for Edge Meetings
The Pitfall: When calculating the free space created by moving the first or last meeting, developers might incorrectly use the general formula without considering the special boundaries:
# INCORRECT - not handling boundaries properly left_boundary = end_time[i - 1] # This fails when i = 0! right_boundary = start_time[i + 1] # This fails when i = n-1!
The Solution: Always check and handle edge cases explicitly:
# CORRECT - proper boundary handling left_boundary = 0 if i == 0 else end_time[i - 1] right_boundary = event_time if i == n - 1 else start_time[i + 1]
4. Off-by-One Errors in Precomputation Arrays
The Pitfall:
When building the max_gap_before
and max_gap_after
arrays, it's easy to mess up the indices, especially in the backward iteration:
# INCORRECT - wrong range in backward iteration
for i in range(n - 1, 0, -1): # Misses index 0!
max_gap_after[i] = max(...)
The Solution: Ensure the ranges cover all necessary indices:
# CORRECT - proper range for backward iteration
for i in range(n - 2, -1, -1): # Goes from n-2 down to 0
max_gap_after[i] = max(
max_gap_after[i + 1],
start_time[i + 1] - end_time[i]
)
Which of the following is a min heap?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!