Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@
* [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py)
* [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py)
* Intervals
* Count Days
* [Test Count Days Without Meetings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/count_days/test_count_days_without_meetings.py)
* Insert Interval
* [Test Insert Interval](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/insert_interval/test_insert_interval.py)
* Interval Intersection
Expand Down
65 changes: 65 additions & 0 deletions algorithms/intervals/count_days/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Count Days Without Meetings

You are given a positive integer, `days`, which represents the total number of days an employee is available for work,
starting from day 1. You are also given a 2D array, `meetings`, where each entry meetings[i] = [starti, endi] indicates
that a meeting is scheduled from day starti to day endi (both inclusive).

Your task is to count the days when the employee is available for work but has no scheduled meetings.

> Note: The meetings may overlap.

## Constraints

- 1 <= days <= 10^5
- 1 <= meetings.length <= 10^3
- meetings[i].length == 2
- 1 <= `meetings[i][0]` <= `meetings[i][1]` <= days

## Examples

![Example 1](./images/examples/count_days_without_meetings_example_1.png)
![Example 2](./images/examples/count_days_without_meetings_example_2.png)
![Example 3](./images/examples/count_days_without_meetings_example_3.png)

## Solution

The core idea of this solution is to merge overlapping meetings into continuous intervals to efficiently track the
occupied days. We begin by sorting the meetings to process them sequentially. As we iterate, we merge overlapping
meetings while counting the occupied days whenever gaps appear. Finally, subtracting the total occupied days from the
available days gives the number of free days.

Using the intuition above, we implement the algorithm as follows:

1. First, sort the meetings based on their start time to process them in order.
2. Initialize a variable, occupied, with 0 to count the days when the employee has scheduled meetings.
3. Initialize two variables, start and end, with the first meeting’s start and end times. These variables define the
beginning and end of the merged meeting interval to efficiently track continuously occupied periods.
4. Iterate through the remaining meetings:
- If a meeting overlaps with the current merged meeting, extend the end time to merge it into the existing interval.
- Otherwise, add the days of the merged meeting to occupied as `occupied = occupied + (end - start + 1)`. Then, update
the start and end for the next interval.
5. After the loop, add the days of the last merged interval to occupied.
6. Return the difference between days and occupied (`days−occupied`), representing the number of days when the employee
is available for work but has no scheduled meetings.

![Solution 1](./images/solutions/count_days_without_meetings_solution_1.png)
![Solution 2](./images/solutions/count_days_without_meetings_solution_2.png)
![Solution 3](./images/solutions/count_days_without_meetings_solution_3.png)
![Solution 4](./images/solutions/count_days_without_meetings_solution_4.png)
![Solution 5](./images/solutions/count_days_without_meetings_solution_5.png)
![Solution 6](./images/solutions/count_days_without_meetings_solution_6.png)
![Solution 7](./images/solutions/count_days_without_meetings_solution_7.png)
![Solution 8](./images/solutions/count_days_without_meetings_solution_8.png)
![Solution 9](./images/solutions/count_days_without_meetings_solution_9.png)
![Solution 10](./images/solutions/count_days_without_meetings_solution_10.png)
![Solution 11](./images/solutions/count_days_without_meetings_solution_11.png)
![Solution 12](./images/solutions/count_days_without_meetings_solution_12.png)

### Time Complexity

The algorithm’s time complexity is O(nlogn), where n is the size of the meetings array. This is due to the sorting step,
which dominates the overall complexity while merging the intervals runs in O(n).

### Space Complexity

The algorithm’s space complexity is constant, O(1).
110 changes: 110 additions & 0 deletions algorithms/intervals/count_days/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from typing import List


def count_days(days: int, meetings: List[List[int]]) -> int:
"""
Counts the number of days the employee is available for work but has no scheduled meetings.

Complexity:

Standard time complexity for sorting is O(n log(n)). The loop is O(n). The part that dominates the overall time
complexity is the sorting and the loop. It amounts to O(n log(n)) as the overall. The overall space complexity for
this approach is O(1) without accounting for the in place sorting that is taking place which is O(n) using timsort
in Python.

Summary of Performance
---
Metric Complexity Reason
---
Time O(nlogn) Dominated by the initial sort of n meetings.
Space O(n) Required by Timsort for internal temporary storage.

Args:
days (int): The total number of days the employee is available for work
meetings (List[List[int]]): A list of meetings, where each meeting is represented as a list of two integers [start, end]
Returns:
int: The number of days the employee is available for work but has no scheduled meetings
"""
# sort meetings by start in place, incurs O(n log(n)) time complexity
meetings.sort(key=lambda x: x[0])

# keep track of free days
free_days = 0

# a pointer that keeps track of the current day
last_busy_day = 0

# iterate through the meetings, for each meeting we might have a gap
for meeting in meetings:
# get the start and end of the meeting
start, end = meeting

# calculate gaps, if the meeting starts at start and our last_busy_day is less than start - 1, the days in
# between are free
if start > last_busy_day + 1:
free_days += (start - 1) - last_busy_day

# update the last busy day to the maximum of the last busy day and the end of the meeting

# This ensures that if a meeting is completely contained within a previous busy block, the boundary doesn't move
# backward, which handles overlaps perfectly.
last_busy_day = max(last_busy_day, end)

# add the remaining days to the free days
return free_days + (days - last_busy_day)


def count_days_2(days: int, meetings: List[List[int]]) -> int:
"""
Counts the number of days the employee is available for work but has no scheduled meetings.

This implementation merges overlapping meetings and counts total occupied days.

Time Complexity: O(n log n) due to sorting
Space Complexity: O(1) excluding sort overhead

Args:
days (int): The total number of days the employee is available for work
meetings (List[List[int]]): A list of meetings, where each meeting is represented as a list of two integers [start, end]
Note: This function modifies the input list by sorting it in place.
Returns:
int: The number of days the employee is available for work but has no scheduled meetings
"""
# Sort the meetings based on their start time to process them in order
meetings.sort()

# Initialize a variable with 0 to count the number of days when the employee has meetings scheduled
occupied = 0

# Initialize two variables with the first meeting’s start and end times
# Sort the meetings based on their start time to process them in order
meetings.sort()

# Handle edge case of empty meetings
if not meetings:
return days

# Initialize a variable with 0 to count the number of days when the employee has meetings scheduled
occupied = 0

# Initialize two variables with the first meeting's start and end times
start, end = meetings[0]

# Iterate through the remaining meetings
for i in range(1, len(meetings)):
# If a meeting overlaps with the current merged meeting
if meetings[i][0] <= end:
# Extend the end time to merge it
end = max(end, meetings[i][1])
else:
# Add the days of the merged meeting
occupied += end - start + 1

# Update start and end for the next interval
start, end = meetings[i]

# Add the days of the last merged meeting
occupied += end - start + 1

# Return the free days
return days - occupied
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading