LeetCode 1701 - Average Waiting Time
This problem is asking us to simulate the operation of a single-chef restaurant, where customers arrive at certain times and each customer has a specific preparation time for their order. The goal is to calculate the average waiting time for all customers.
Difficulty: 🟡 Medium
Topics: Array, Simulation
Solution
Problem Understanding
This problem is asking us to simulate the operation of a single-chef restaurant, where customers arrive at certain times and each customer has a specific preparation time for their order. The goal is to calculate the average waiting time for all customers. The waiting time for a customer is the total time they spend in the system from arrival until their order is finished.
The input customers is a 2D array where each element [arrivali, timei] represents a single customer. arrivali is the exact time the customer arrives, and timei is the amount of time the chef needs to prepare their order. The customers are guaranteed to be sorted by arrival time in non-decreasing order. The expected output is a floating-point number representing the average waiting time across all customers. The answer must be precise within 10^-5.
Constraints tell us that the number of customers can be as large as 10^5, and the time values can go up to 10^4. This indicates that we need a linear-time solution rather than anything with nested loops over the customer list. The constraints guarantee that the arrival times are non-decreasing, so we do not have to handle sorting or out-of-order arrivals. Edge cases include customers arriving at the same time, very large preparation times, or customers arriving long after the previous order has finished.
Approaches
The brute-force approach would simulate time second by second, keeping track of when the chef is busy and when each order finishes. For each second, you would check if the chef is idle and if any customer is waiting. While this approach gives the correct answer, it is extremely inefficient, especially when arrivali can be as large as 10^4 and customers.length can be 10^5. It could take up to 10^9 steps in the worst case, which is far too slow.
The optimal approach leverages the fact that customers are already sorted by arrival time and that the chef only works on one order at a time. We maintain a single variable, current_time, representing when the chef will be free. For each customer, the chef starts preparing their order at the later of the customer's arrival time and current_time. We then compute the waiting time as the difference between the order completion time and the arrival time. This approach only requires a single pass through the customer list.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(max(arrival) * n) | O(1) | Simulate each second, check chef and customers |
| Optimal | O(n) | O(1) | Track chef availability and compute waiting time in one pass |
Algorithm Walkthrough
- Initialize
current_timeto0. This variable represents when the chef will finish the previous order or be ready to start a new one. - Initialize
total_waiting_timeto0. This variable accumulates the total waiting time of all customers. - Iterate through each customer
[arrival, time]in thecustomerslist. - For each customer, calculate the start time of their order as
max(current_time, arrival). This ensures the chef starts as soon as possible but never before the customer arrives. - Compute the finish time of the order as
start_time + time. - Calculate the waiting time as
finish_time - arrivaland add it tototal_waiting_time. - Update
current_timetofinish_timefor the next iteration. - After processing all customers, compute the average waiting time as
total_waiting_time / number_of_customers. - Return the average waiting time as a floating-point number.
This algorithm works because at each step, current_time accurately represents when the chef will be free. By taking max(current_time, arrival), we ensure the chef never starts before the customer arrives. Summing all waiting times and dividing by the total number of customers guarantees the correct average.
Python Solution
from typing import List
class Solution:
def averageWaitingTime(self, customers: List[List[int]]) -> float:
current_time = 0
total_waiting_time = 0
for arrival, time in customers:
start_time = max(current_time, arrival)
finish_time = start_time + time
total_waiting_time += finish_time - arrival
current_time = finish_time
return total_waiting_time / len(customers)
In this implementation, current_time keeps track of when the chef becomes available. For each customer, we compute the earliest start time of their order using max(current_time, arrival), calculate the finish time, and increment the cumulative waiting time. Finally, dividing by the number of customers gives the required average.
Go Solution
func averageWaitingTime(customers [][]int) float64 {
currentTime := 0
totalWaitingTime := 0
for _, customer := range customers {
arrival, time := customer[0], customer[1]
startTime := arrival
if currentTime > arrival {
startTime = currentTime
}
finishTime := startTime + time
totalWaitingTime += finishTime - arrival
currentTime = finishTime
}
return float64(totalWaitingTime) / float64(len(customers))
}
In Go, we handle integer arithmetic carefully and convert the final sum to float64 before dividing to get a floating-point average. Otherwise, the logic is identical to Python.
Worked Examples
Example 1: [[1,2],[2,5],[4,3]]
| Customer | Arrival | Time | Start | Finish | Waiting | Current Time |
|---|---|---|---|---|---|---|
| 1 | 1 | 2 | 1 | 3 | 2 | 3 |
| 2 | 2 | 5 | 3 | 8 | 6 | 8 |
| 3 | 4 | 3 | 8 | 11 | 7 | 11 |
Average waiting time = (2 + 6 + 7) / 3 = 5.0
Example 2: [[5,2],[5,4],[10,3],[20,1]]
| Customer | Arrival | Time | Start | Finish | Waiting | Current Time |
|---|---|---|---|---|---|---|
| 1 | 5 | 2 | 5 | 7 | 2 | 7 |
| 2 | 5 | 4 | 7 | 11 | 6 | 11 |
| 3 | 10 | 3 | 11 | 14 | 4 | 14 |
| 4 | 20 | 1 | 20 | 21 | 1 | 21 |
Average waiting time = (2 + 6 + 4 + 1) / 4 = 3.25
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Iterate once through all customers |
| Space | O(1) | Only a few variables are used, no extra storage |
This algorithm is optimal because it processes each customer exactly once and uses constant space.
Test Cases
# Provided examples
assert abs(Solution().averageWaitingTime([[1,2],[2,5],[4,3]]) - 5.0) < 1e-5 # Example 1
assert abs(Solution().averageWaitingTime([[5,2],[5,4],[10,3],[20,1]]) - 3.25) < 1e-5 # Example 2
# Edge cases
assert abs(Solution().averageWaitingTime([[1,1]]) - 1.0) < 1e-5 # Single customer
assert abs(Solution().averageWaitingTime([[1,2],[1,3],[1,4]]) - 5.0) < 1e-5 # Multiple arrivals at same time
assert abs(Solution().averageWaitingTime([[1,10000],[10001,1]]) - 10000.5) < 1e-5 # Large time values
assert abs(Solution().averageWaitingTime([[1,2],[10,3],[20,4]]) - 3.0) < 1e-5 # Sparse arrivals
| Test | Why |
|---|---|
| Single customer | Validates minimum input |
| Multiple arrivals at same time | Checks handling of simultaneous arrivals |
| Large time values | Ensures no integer overflow issues |
| Sparse arrivals | Verifies chef waits correctly for next customer |
Edge Cases
- Single Customer: If there is only one customer, the waiting time is simply the preparation time. The implementation correctly calculates this because
current_timestarts at 0, and the algorithm computesfinish_time - arrival. - Customers Arriving Simultaneously: Multiple customers arriving at the same time can be tricky because the chef can only work on one at a time. Using
max(current_time, arrival)ensures that the chef processes them sequentially in the correct order. - Large Gaps Between Customers: If the next customer arrives after the chef has been idle, the chef should wait until the customer arrives. This is handled because
start_time = max(current_time, arrival)will pick the customer’s arrival