Skip to content

Commit 7d2e851

Browse files
authored
Merge pull request #128 from BrianLusina/feat/intervals-count-days-without-meetings
feat(algorithms, intervals): count days without meetings
2 parents 3f1b0db + 33819d4 commit 7d2e851

20 files changed

+710
-2
lines changed

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
* [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py)
102102
* [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py)
103103
* Intervals
104+
* Count Days
105+
* [Test Count Days Without Meetings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/count_days/test_count_days_without_meetings.py)
104106
* Insert Interval
105107
* [Test Insert Interval](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/insert_interval/test_insert_interval.py)
106108
* Interval Intersection
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Count Days Without Meetings
2+
3+
You are given a positive integer, `days`, which represents the total number of days an employee is available for work,
4+
starting from day 1. You are also given a 2D array, `meetings`, where each entry meetings[i] = [starti, endi] indicates
5+
that a meeting is scheduled from day starti to day endi (both inclusive).
6+
7+
Your task is to count the days when the employee is available for work but has no scheduled meetings.
8+
9+
> Note: The meetings may overlap.
10+
11+
## Constraints
12+
13+
- 1 <= days <= 10^5
14+
- 1 <= meetings.length <= 10^3
15+
- meetings[i].length == 2
16+
- 1 <= `meetings[i][0]` <= `meetings[i][1]` <= days
17+
18+
## Examples
19+
20+
![Example 1](./images/examples/count_days_without_meetings_example_1.png)
21+
![Example 2](./images/examples/count_days_without_meetings_example_2.png)
22+
![Example 3](./images/examples/count_days_without_meetings_example_3.png)
23+
24+
## Solution
25+
26+
The core idea of this solution is to merge overlapping meetings into continuous intervals to efficiently track the
27+
occupied days. We begin by sorting the meetings to process them sequentially. As we iterate, we merge overlapping
28+
meetings while counting the occupied days whenever gaps appear. Finally, subtracting the total occupied days from the
29+
available days gives the number of free days.
30+
31+
Using the intuition above, we implement the algorithm as follows:
32+
33+
1. First, sort the meetings based on their start time to process them in order.
34+
2. Initialize a variable, occupied, with 0 to count the days when the employee has scheduled meetings.
35+
3. Initialize two variables, start and end, with the first meeting’s start and end times. These variables define the
36+
beginning and end of the merged meeting interval to efficiently track continuously occupied periods.
37+
4. Iterate through the remaining meetings:
38+
- If a meeting overlaps with the current merged meeting, extend the end time to merge it into the existing interval.
39+
- Otherwise, add the days of the merged meeting to occupied as `occupied = occupied + (end - start + 1)`. Then, update
40+
the start and end for the next interval.
41+
5. After the loop, add the days of the last merged interval to occupied.
42+
6. Return the difference between days and occupied (`days−occupied`), representing the number of days when the employee
43+
is available for work but has no scheduled meetings.
44+
45+
![Solution 1](./images/solutions/count_days_without_meetings_solution_1.png)
46+
![Solution 2](./images/solutions/count_days_without_meetings_solution_2.png)
47+
![Solution 3](./images/solutions/count_days_without_meetings_solution_3.png)
48+
![Solution 4](./images/solutions/count_days_without_meetings_solution_4.png)
49+
![Solution 5](./images/solutions/count_days_without_meetings_solution_5.png)
50+
![Solution 6](./images/solutions/count_days_without_meetings_solution_6.png)
51+
![Solution 7](./images/solutions/count_days_without_meetings_solution_7.png)
52+
![Solution 8](./images/solutions/count_days_without_meetings_solution_8.png)
53+
![Solution 9](./images/solutions/count_days_without_meetings_solution_9.png)
54+
![Solution 10](./images/solutions/count_days_without_meetings_solution_10.png)
55+
![Solution 11](./images/solutions/count_days_without_meetings_solution_11.png)
56+
![Solution 12](./images/solutions/count_days_without_meetings_solution_12.png)
57+
58+
### Time Complexity
59+
60+
The algorithm’s time complexity is O(nlogn), where n is the size of the meetings array. This is due to the sorting step,
61+
which dominates the overall complexity while merging the intervals runs in O(n).
62+
63+
### Space Complexity
64+
65+
The algorithm’s space complexity is constant, O(1).
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from typing import List
2+
3+
4+
def count_days(days: int, meetings: List[List[int]]) -> int:
5+
"""
6+
Counts the number of days the employee is available for work but has no scheduled meetings.
7+
8+
Complexity:
9+
10+
Standard time complexity for sorting is O(n log(n)). The loop is O(n). The part that dominates the overall time
11+
complexity is the sorting and the loop. It amounts to O(n log(n)) as the overall. The overall space complexity for
12+
this approach is O(1) without accounting for the in place sorting that is taking place which is O(n) using timsort
13+
in Python.
14+
15+
Summary of Performance
16+
---
17+
Metric Complexity Reason
18+
---
19+
Time O(nlogn) Dominated by the initial sort of n meetings.
20+
Space O(n) Required by Timsort for internal temporary storage.
21+
22+
Args:
23+
days (int): The total number of days the employee is available for work
24+
meetings (List[List[int]]): A list of meetings, where each meeting is represented as a list of two integers [start, end]
25+
Returns:
26+
int: The number of days the employee is available for work but has no scheduled meetings
27+
"""
28+
# sort meetings by start in place, incurs O(n log(n)) time complexity
29+
meetings.sort(key=lambda x: x[0])
30+
31+
# keep track of free days
32+
free_days = 0
33+
34+
# a pointer that keeps track of the current day
35+
last_busy_day = 0
36+
37+
# iterate through the meetings, for each meeting we might have a gap
38+
for meeting in meetings:
39+
# get the start and end of the meeting
40+
start, end = meeting
41+
42+
# calculate gaps, if the meeting starts at start and our last_busy_day is less than start - 1, the days in
43+
# between are free
44+
if start > last_busy_day + 1:
45+
free_days += (start - 1) - last_busy_day
46+
47+
# update the last busy day to the maximum of the last busy day and the end of the meeting
48+
49+
# This ensures that if a meeting is completely contained within a previous busy block, the boundary doesn't move
50+
# backward, which handles overlaps perfectly.
51+
last_busy_day = max(last_busy_day, end)
52+
53+
# add the remaining days to the free days
54+
return free_days + (days - last_busy_day)
55+
56+
57+
def count_days_2(days: int, meetings: List[List[int]]) -> int:
58+
"""
59+
Counts the number of days the employee is available for work but has no scheduled meetings.
60+
61+
This implementation merges overlapping meetings and counts total occupied days.
62+
63+
Time Complexity: O(n log n) due to sorting
64+
Space Complexity: O(1) excluding sort overhead
65+
66+
Args:
67+
days (int): The total number of days the employee is available for work
68+
meetings (List[List[int]]): A list of meetings, where each meeting is represented as a list of two integers [start, end]
69+
Note: This function modifies the input list by sorting it in place.
70+
Returns:
71+
int: The number of days the employee is available for work but has no scheduled meetings
72+
"""
73+
# Sort the meetings based on their start time to process them in order
74+
meetings.sort()
75+
76+
# Initialize a variable with 0 to count the number of days when the employee has meetings scheduled
77+
occupied = 0
78+
79+
# Initialize two variables with the first meeting’s start and end times
80+
# Sort the meetings based on their start time to process them in order
81+
meetings.sort()
82+
83+
# Handle edge case of empty meetings
84+
if not meetings:
85+
return days
86+
87+
# Initialize a variable with 0 to count the number of days when the employee has meetings scheduled
88+
occupied = 0
89+
90+
# Initialize two variables with the first meeting's start and end times
91+
start, end = meetings[0]
92+
93+
# Iterate through the remaining meetings
94+
for i in range(1, len(meetings)):
95+
# If a meeting overlaps with the current merged meeting
96+
if meetings[i][0] <= end:
97+
# Extend the end time to merge it
98+
end = max(end, meetings[i][1])
99+
else:
100+
# Add the days of the merged meeting
101+
occupied += end - start + 1
102+
103+
# Update start and end for the next interval
104+
start, end = meetings[i]
105+
106+
# Add the days of the last merged meeting
107+
occupied += end - start + 1
108+
109+
# Return the free days
110+
return days - occupied
58.2 KB
Loading
55.8 KB
Loading
62 KB
Loading
65.4 KB
Loading
63.9 KB
Loading
83 KB
Loading
75.9 KB
Loading

0 commit comments

Comments
 (0)