LeetCode 3633 - Earliest Finish Time for Land and Water Rides I
The problem asks us to determine the earliest possible time a tourist can finish exactly one land ride and one water ride at a theme park, where rides may be taken in either order.
Difficulty: 🟢 Easy
Topics: Array, Two Pointers, Binary Search, Greedy, Sorting
Solution
Problem Understanding
The problem asks us to determine the earliest possible time a tourist can finish exactly one land ride and one water ride at a theme park, where rides may be taken in either order. Each ride has a start time, representing when it becomes available, and a duration, representing how long it takes to complete. A ride can start at its opening time or any later time, which means the tourist may have to wait if they finish a ride before the next ride is available.
The inputs are four arrays: landStartTime and landDuration for the land rides, and waterStartTime and waterDuration for the water rides. The output is a single integer representing the earliest finish time after completing one ride from each category.
Key points to note:
- The tourist may take the rides in any order, so we must consider both sequences: land→water and water→land.
- If a ride has not opened when the tourist finishes the previous ride, they must wait until its start time.
- The constraints are small (
1 <= n, m <= 100), so a solution with a nested iteration over both ride lists is feasible.
Important edge cases include:
- Only one ride in either category, forcing the order.
- The second ride opens after the first finishes, requiring a wait.
- All rides in one category are later than all rides in the other category, testing the correct calculation of wait times.
Approaches
Brute Force
The brute-force approach iterates over every possible pair of land and water rides, considering both possible orders: land→water and water→land. For each pair, the finish time is computed as the sum of the first ride's duration plus the start time of the second ride (or the time we finish the first ride, whichever is later). This ensures that we respect ride start times and durations. The minimum of all calculated finish times is the answer.
While this approach is correct, it involves iterating over n * m ride pairs and evaluating two sequences per pair, giving O(n * m) time complexity. Given the constraints, this is acceptable, but it is still worth exploring a slightly optimized approach.
Optimal Approach
The key insight is that for each ride in one category, the earliest possible second ride is the one that opens the soonest after finishing the first ride. Sorting both ride lists by start time allows us to efficiently determine, for each first ride, which second ride yields the earliest finish using binary search or a simple linear scan.
Because the constraints are small (max 100 rides per category), a simple nested iteration over both sorted arrays without binary search is sufficient and keeps the solution readable. This gives the same O(n * m) time complexity but reduces unnecessary comparisons in practice because we process rides in start-time order.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n * m) | O(1) | Check all ride pairs in both orders; simple but exhaustive |
| Optimal | O(n * m) | O(1) | Sort rides (optional) and iterate efficiently; same complexity in worst-case but can be faster in practice |
Algorithm Walkthrough
- Initialize a variable
earliest_finishwith infinity to track the minimum finish time found. - Iterate over each land ride
iand each water ridej. - For each pair
(i, j), compute two possible sequences:
- Land → Water: Start the land ride at
landStartTime[i]. Finish atlandStartTime[i] + landDuration[i]. Start the water ride atmax(finish_land, waterStartTime[j])and finish atstart_water + waterDuration[j]. - Water → Land: Start the water ride at
waterStartTime[j]. Finish atwaterStartTime[j] + waterDuration[j]. Start the land ride atmax(finish_water, landStartTime[i])and finish atstart_land + landDuration[i].
- Update
earliest_finishif either sequence gives a smaller finish time. - After iterating all pairs, return
earliest_finish.
Why it works: The algorithm checks all feasible ride pairings and both possible orders. By considering the maximum of the previous finish time and the next ride's start time, it ensures that rides cannot start before their availability, guaranteeing the earliest finish is correctly identified.
Python Solution
from typing import List
class Solution:
def earliestFinishTime(self, landStartTime: List[int], landDuration: List[int], waterStartTime: List[int], waterDuration: List[int]) -> int:
earliest_finish = float('inf')
n = len(landStartTime)
m = len(waterStartTime)
for i in range(n):
for j in range(m):
# Land -> Water
finish_land = landStartTime[i] + landDuration[i]
start_water = max(finish_land, waterStartTime[j])
finish_water = start_water + waterDuration[j]
earliest_finish = min(earliest_finish, finish_water)
# Water -> Land
finish_water_first = waterStartTime[j] + waterDuration[j]
start_land = max(finish_water_first, landStartTime[i])
finish_land_second = start_land + landDuration[i]
earliest_finish = min(earliest_finish, finish_land_second)
return earliest_finish
The code iterates through every combination of land and water rides, computing both orderings. It uses max to handle waiting for a ride to open and maintains the minimum finish time across all combinations.
Go Solution
func earliestFinishTime(landStartTime []int, landDuration []int, waterStartTime []int, waterDuration []int) int {
earliestFinish := 1 << 30 // large number as infinity
n := len(landStartTime)
m := len(waterStartTime)
for i := 0; i < n; i++ {
for j := 0; j < m; j++ {
// Land -> Water
finishLand := landStartTime[i] + landDuration[i]
startWater := finishLand
if waterStartTime[j] > finishLand {
startWater = waterStartTime[j]
}
finishWater := startWater + waterDuration[j]
if finishWater < earliestFinish {
earliestFinish = finishWater
}
// Water -> Land
finishWaterFirst := waterStartTime[j] + waterDuration[j]
startLand := finishWaterFirst
if landStartTime[i] > finishWaterFirst {
startLand = landStartTime[i]
}
finishLandSecond := startLand + landDuration[i]
if finishLandSecond < earliestFinish {
earliestFinish = finishLandSecond
}
}
}
return earliestFinish
}
In Go, we use 1 << 30 as an effectively infinite starting value. Conditional statements replace Python's max for clarity. The nested loops and update logic remain the same.
Worked Examples
Example 1
Input: landStartTime = [2,8], landDuration = [4,1], waterStartTime = [6], waterDuration = [3]
| Land Ride | Water Ride | Sequence | Finish Time |
|---|---|---|---|
| 0 | 0 | Land→Water | 9 |
| 0 | 0 | Water→Land | 13 |
| 1 | 0 | Land→Water | 12 |
| 1 | 0 | Water→Land | 10 |
Earliest finish time = 9
Example 2
Input: landStartTime = [5], landDuration = [3], waterStartTime = [1], waterDuration = [10]
| Land Ride | Water Ride | Sequence | Finish Time |
|---|---|---|---|
| 0 | 0 | Land→Water | 18 |
| 0 | 0 | Water→Land | 14 |
Earliest finish time = 14
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * m) | Nested loops over all land and water ride pairs; constant time per pair |
| Space | O(1) | Only a few integer variables used for tracking minimum finish time |
The algorithm scales quadratically with the number of rides but remains efficient given the constraint of maximum 100 rides per category.
Test Cases
# Basic examples
assert Solution().earliestFinishTime([2,8], [4,1], [6], [3]) == 9 # Example 1
assert Solution().earliestFinishTime([5], [3], [1], [10]) == 14 # Example 2
# Single ride in each category
assert Solution().earliestFinishTime([1], [1], [1], [1]) == 2
# Second ride opens after first finishes
assert Solution().earliestFinishTime([1], [3], [10], [2]) == 12
# All land rides earlier than water rides
assert Solution().earliestFinishTime([1,2,3], [1,1,1], [10,11], [2,2]) == 12
# All water rides earlier than land rides
assert Solution().earliestFinishTime([10,11], [2