diff --git a/DIRECTORY.md b/DIRECTORY.md index c4be82d0..79fa0c50 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -106,6 +106,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 + * Car Pooling + * [Test Car Pooling](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/car_pooling/test_car_pooling.py) * Count Days * [Test Count Days Without Meetings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/count_days/test_count_days_without_meetings.py) * Full Bloom Flowers diff --git a/algorithms/intervals/car_pooling/README.md b/algorithms/intervals/car_pooling/README.md new file mode 100644 index 00000000..a4403e98 --- /dev/null +++ b/algorithms/intervals/car_pooling/README.md @@ -0,0 +1,97 @@ +# Car Pooling + +You are given a car with a fixed number of seats, denoted by an integer capacity. The car only travels in one direction +— eastward — and does not make any U-turns. + +You are also provided with an array, trips, where each element trips[i]= [numPassengersi, fromi, toi] represents a group of +numPassengersi that must be picked up at location fromi and dropped off at location toi . All locations are measured in +kilometers east of the starting point. + +Your task is to determine whether it is possible to complete all the trips without exceeding the car’s capacity at any +point in time. + +Return TRUE if all trips can be completed successfully, or FALSE otherwise. + +## Constraints + +- 1 <= `trips.length` <= 1000 +- `trips[i].length` == 3 +- 1 <= `numPassengers` <= 1000 +- 0 <= fromi < toi <= 1000 +- 1 <= `capacity` <= 10^5 + +## Examples + +![Example 1](./images/examples/car_pooling_example_1.png) +![Example 2](./images/examples/car_pooling_example_2.png) +![Example 3](./images/examples/car_pooling_example_3.png) + + +## Solution + +The core intuition behind this solution is to simulate the journey of the car using a timeline, tracking when passengers +get in and get out. This approach aligns with the merge intervals pattern, where we focus on events happening at specific +points in time, rather than handling each trip separately in isolation. The idea is to accumulate and monitor the number +of passengers in the car at any moment using a difference array (also known as a prefix sum technique). + +In the context of this problem, each trip defines a passenger change over a specific interval — passengers are picked up +at from and dropped off at to. Instead of iterating over the entire interval (which is inefficient), we can track just +the start and end of the interval using a fixed-size timeline array. + +This strategy is built on two key observations: + +1. Each trip contributes a net passenger change at exactly two locations: + - Increase at the pickup location (from) + - Decrease at the drop-off location (to) + +2. The car’s capacity must never be exceeded at any point on the journey, so we monitor the cumulative passenger count + as we move forward in time. + +This strategy leverages the difference array pattern, which is a version of merge intervals. Rather than merging +overlapping intervals explicitly, it simulates all trips together via a timeline of passenger changes. + +Let’s break down the steps: + +1. A list timestamp of size 1001 (based on constraints) is created to track changes in passenger counts at each kilometer + point. +2. For each trip `[numPassengersi ,fromi, toi]` in trips: + - Add numPassengers at `timestamp[fromi]` + - Subtract numPassengers at `timestamp[toi]` + + This marks the start and end of each passenger interval without iterating over the full range. + +3. To simulate the trip and track capacity, initialize a variable used_capacity = 0. +4. Iterate through the timestamp list, adding the passenger changes at each point. If at any point used_capacity > capacity, + return FALSE immediately— the car is overloaded. +5. If the full timeline is processed without exceeding capacity, return TRUE, indicating that all trips can be accommodated. + +Let’s look at the following illustration to get a better understanding of the solution: + +![Solution 1](./images/solutions/car_pooling_solution_1.png) +![Solution 2](./images/solutions/car_pooling_solution_2.png) +![Solution 3](./images/solutions/car_pooling_solution_3.png) +![Solution 4](./images/solutions/car_pooling_solution_4.png) +![Solution 5](./images/solutions/car_pooling_solution_5.png) +![Solution 6](./images/solutions/car_pooling_solution_6.png) +![Solution 7](./images/solutions/car_pooling_solution_7.png) +![Solution 8](./images/solutions/car_pooling_solution_8.png) +![Solution 9](./images/solutions/car_pooling_solution_9.png) +![Solution 10](./images/solutions/car_pooling_solution_10.png) +![Solution 11](./images/solutions/car_pooling_solution_11.png) +![Solution 12](./images/solutions/car_pooling_solution_12.png) + +### Time Complexity + +The overall time complexity of the solution is O(n) because: + +- The trips list is scanned once to record passenger changes at pickup and drop-off points. +- Each trip results in two constant-time operations: one increment and one decrement in the timeline array. +- The timestamp array (of fixed length 1001) is traversed once to simulate the journey and check capacity constraints. + +As the length of timestamp is constant and independent of input size, it contributes O(1) time. So, the total operations +scale linearly with the number of trips. + +### Space Complexity + +The space complexity of the solution is O(1) because a fixed-size array timestamp of length 1001 is used regardless of +the input size. diff --git a/algorithms/intervals/car_pooling/__init__.py b/algorithms/intervals/car_pooling/__init__.py new file mode 100644 index 00000000..d99caa53 --- /dev/null +++ b/algorithms/intervals/car_pooling/__init__.py @@ -0,0 +1,60 @@ +from typing import List, Tuple + + +def car_pooling(trips: List[List[int]], capacity: int) -> bool: + """ + Calculates and checks whether it is possible to pick up and drop off passengers on the given trips given the car's + capacity + Args: + trips(list): The trips that the car makes while collecting passengers on the route + capacity(int): capacity of the car + Returns: + bool: whether it is possible to collect and drop all passengers along the route + """ + # For every trip, create two entries, the start with the passenger count and end with the passenger count + events: List[Tuple[int, int]] = [] + + # Create the events + for trip in trips: + num, start, end = trip + # Mark the increase and decrease in passengers at pickup and drop-off points + events.append((start, num)) # pick up + events.append((end, -num)) # drop off + + # sort by the location + # If locations are equal, the negative value (drop-off) + # will naturally come before the positive value (pick-up) + events.sort() + + # This keeps track of the current vehicle's capacity, which will be used along the trip to check how many passengers + # have been carried so far + current_occupancy = 0 + + # process events chronologically + for event in events: + location, change = event + current_occupancy += change + if current_occupancy > capacity: + return False + + return True + + +def car_pooling_bucket(trips: List[List[int]], capacity: int) -> bool: + # Initialize a timeline to track changes in passenger count at each km mark (0 to 1000) + timestamp = [0] * 1001 + + # Mark the increase and decrease in passengers at pickup and drop-off points + for trip in trips: + num_passengers, start, end = trip + timestamp[start] += num_passengers # Pick up passengers + timestamp[end] -= num_passengers # Drop off passengers + + # Simulate the car's journey by applying the passenger changes + used_capacity = 0 + for passenger_change in timestamp: + used_capacity += passenger_change # Update current passenger count + if used_capacity > capacity: # Check if capacity is exceeded + return False # Trip configuration is invalid + + return True # All trips are valid within capacity diff --git a/algorithms/intervals/car_pooling/images/examples/car_pooling_example_1.png b/algorithms/intervals/car_pooling/images/examples/car_pooling_example_1.png new file mode 100644 index 00000000..96c16fc0 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/examples/car_pooling_example_1.png differ diff --git a/algorithms/intervals/car_pooling/images/examples/car_pooling_example_2.png b/algorithms/intervals/car_pooling/images/examples/car_pooling_example_2.png new file mode 100644 index 00000000..66ffbce6 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/examples/car_pooling_example_2.png differ diff --git a/algorithms/intervals/car_pooling/images/examples/car_pooling_example_3.png b/algorithms/intervals/car_pooling/images/examples/car_pooling_example_3.png new file mode 100644 index 00000000..de16607a Binary files /dev/null and b/algorithms/intervals/car_pooling/images/examples/car_pooling_example_3.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_1.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_1.png new file mode 100644 index 00000000..a23cc8c3 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_1.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_10.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_10.png new file mode 100644 index 00000000..1ccbc883 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_10.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_11.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_11.png new file mode 100644 index 00000000..78477089 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_11.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_12.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_12.png new file mode 100644 index 00000000..1e98f778 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_12.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_2.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_2.png new file mode 100644 index 00000000..0a542c5b Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_2.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_3.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_3.png new file mode 100644 index 00000000..460ddc66 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_3.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_4.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_4.png new file mode 100644 index 00000000..69a324dc Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_4.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_5.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_5.png new file mode 100644 index 00000000..ec267efa Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_5.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_6.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_6.png new file mode 100644 index 00000000..78a4b293 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_6.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_7.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_7.png new file mode 100644 index 00000000..c4e03b4d Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_7.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_8.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_8.png new file mode 100644 index 00000000..bc456ed4 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_8.png differ diff --git a/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_9.png b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_9.png new file mode 100644 index 00000000..25cbbf70 Binary files /dev/null and b/algorithms/intervals/car_pooling/images/solutions/car_pooling_solution_9.png differ diff --git a/algorithms/intervals/car_pooling/test_car_pooling.py b/algorithms/intervals/car_pooling/test_car_pooling.py new file mode 100644 index 00000000..94b0a057 --- /dev/null +++ b/algorithms/intervals/car_pooling/test_car_pooling.py @@ -0,0 +1,34 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.intervals.car_pooling import car_pooling, car_pooling_bucket + +CAR_POOLING_TEST_CASES = [ + ([[3, 2, 6], [1, 4, 7], [2, 5, 8]], 5, False), + ([[2, 0, 4], [3, 2, 6], [1, 5, 8]], 4, False), + ([[1, 0, 3], [2, 2, 5], [3, 4, 6]], 6, True), + ([[2, 1, 5], [3, 3, 7]], 4, False), + ([[2, 1, 5], [3, 3, 7]], 5, True), + ([[3, 2, 6], [1, 4, 7], [2, 5, 8]], 5, False), + ([[1, 0, 4], [2, 2, 6], [3, 5, 8]], 6, True), + ([[50, 0, 50], [51, 1, 51]], 100, False), + ([[25, 0, 4], [25, 2, 6], [25, 5, 8]], 50, True), +] + + +class CarPoolingTestCases(unittest.TestCase): + @parameterized.expand(CAR_POOLING_TEST_CASES) + def test_car_pooling(self, trips: List[List[int]], capacity: int, expected: bool): + actual = car_pooling(trips, capacity) + self.assertEqual(expected, actual) + + @parameterized.expand(CAR_POOLING_TEST_CASES) + def test_car_pooling_bucket_sort( + self, trips: List[List[int]], capacity: int, expected: bool + ): + actual = car_pooling_bucket(trips, capacity) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main()