197 lines
9.8 KiB
YAML
197 lines
9.8 KiB
YAML
title: Average Waiting Time
|
|
slug: average-waiting-time
|
|
difficulty: medium
|
|
leetcode_id: 1701
|
|
leetcode_url: https://leetcode.com/problems/average-waiting-time/
|
|
categories:
|
|
- arrays
|
|
patterns:
|
|
- slug: greedy
|
|
is_optimal: true
|
|
|
|
function_signature: "def average_waiting_time(customers: list[list[int]]) -> float:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { customers: [[1, 2], [2, 5], [4, 3]] }
|
|
expected: 5.0
|
|
- input: { customers: [[5, 2], [5, 4], [10, 3], [20, 1]] }
|
|
expected: 3.25
|
|
hidden:
|
|
- input: { customers: [[1, 1]] }
|
|
expected: 1.0
|
|
- input: { customers: [[1, 5], [2, 3], [3, 2]] }
|
|
expected: 5.0
|
|
- input: { customers: [[1, 1], [2, 1], [3, 1]] }
|
|
expected: 1.0
|
|
- input: { customers: [[1, 10], [1, 10], [1, 10]] }
|
|
expected: 20.0
|
|
- input: { customers: [[10, 5], [20, 5], [30, 5]] }
|
|
expected: 5.0
|
|
|
|
description: |
|
|
There is a restaurant with a single chef. You are given an array `customers`, where `customers[i] = [arrival_i, time_i]`:
|
|
|
|
- `arrival_i` is the arrival time of the i<sup>th</sup> customer. The arrival times are sorted in **non-decreasing** order.
|
|
- `time_i` is the time needed to prepare the order of the i<sup>th</sup> customer.
|
|
|
|
When a customer arrives, they give the chef their order, and the chef starts preparing it once idle. The customer waits until the chef finishes preparing their order. The chef does not prepare food for more than one customer at a time. The chef prepares food for customers **in the order they were given in the input**.
|
|
|
|
Return *the **average** waiting time of all customers*. Solutions within `10^-5` from the actual answer are considered accepted.
|
|
|
|
constraints: |
|
|
- `1 <= customers.length <= 10^5`
|
|
- `1 <= arrival_i, time_i <= 10^4`
|
|
- `arrival_i <= arrival_(i+1)`
|
|
|
|
examples:
|
|
- input: "customers = [[1,2],[2,5],[4,3]]"
|
|
output: "5.00000"
|
|
explanation: |
|
|
1) The first customer arrives at time 1, the chef starts immediately and finishes at time 3. Waiting time = 3 - 1 = 2.
|
|
2) The second customer arrives at time 2, the chef starts at time 3 (after finishing customer 1) and finishes at time 8. Waiting time = 8 - 2 = 6.
|
|
3) The third customer arrives at time 4, the chef starts at time 8 and finishes at time 11. Waiting time = 11 - 4 = 7.
|
|
Average waiting time = (2 + 6 + 7) / 3 = 5.
|
|
- input: "customers = [[5,2],[5,4],[10,3],[20,1]]"
|
|
output: "3.25000"
|
|
explanation: |
|
|
1) Customer 1 arrives at time 5, chef finishes at time 7. Waiting time = 2.
|
|
2) Customer 2 arrives at time 5, chef starts at 7 and finishes at 11. Waiting time = 6.
|
|
3) Customer 3 arrives at time 10, chef starts at 11 and finishes at 14. Waiting time = 4.
|
|
4) Customer 4 arrives at time 20, chef starts immediately and finishes at 21. Waiting time = 1.
|
|
Average waiting time = (2 + 6 + 4 + 1) / 4 = 3.25.
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you're standing in line at a busy restaurant with a single chef. Each customer places an order that takes a certain amount of time to prepare. The chef works through orders one at a time, in the order they arrive.
|
|
|
|
The key insight is understanding what "waiting time" means for each customer: it's the time from when they **arrive** until their food is **ready** — not just the cooking time. If the chef is still busy when you arrive, you wait for them to finish before your order even starts.
|
|
|
|
Think of it like a timeline: the chef maintains a "current time" pointer that moves forward as they complete orders. When a new customer arrives:
|
|
- If the chef is free (current time <= arrival time), they start cooking immediately
|
|
- If the chef is busy (current time > arrival time), the customer must wait until the chef finishes their current order
|
|
|
|
By simulating this process and tracking when each order completes, we can calculate each customer's total waiting time and then average them all.
|
|
|
|
approach: |
|
|
We solve this with a **Single Pass Simulation**:
|
|
|
|
**Step 1: Initialise tracking variables**
|
|
|
|
- `current_time`: Set to `0`, representing when the chef becomes available
|
|
- `total_wait`: Set to `0`, accumulating the sum of all waiting times
|
|
|
|
|
|
|
|
**Step 2: Process each customer in order**
|
|
|
|
For each customer with `[arrival, cook_time]`:
|
|
|
|
- Determine when the chef can start: `max(current_time, arrival)`
|
|
- Calculate when the order will be finished: `start_time + cook_time`
|
|
- Calculate this customer's waiting time: `finish_time - arrival`
|
|
- Add this waiting time to `total_wait`
|
|
- Update `current_time` to `finish_time` for the next customer
|
|
|
|
|
|
|
|
**Step 3: Calculate and return the average**
|
|
|
|
- Return `total_wait / n` where `n` is the number of customers
|
|
|
|
|
|
|
|
This approach works because customers are already sorted by arrival time. We process them in order, maintaining the chef's timeline as we go. Each customer's waiting time is simply how long from their arrival until their food is ready.
|
|
|
|
common_pitfalls:
|
|
- title: Confusing Waiting Time with Cooking Time
|
|
description: |
|
|
A common mistake is returning just the average of all cooking times (`time_i` values). But waiting time includes:
|
|
- Any time spent waiting for the chef to become free
|
|
- Plus the actual cooking time
|
|
|
|
For example, if customer 2 arrives at time 2 but the chef is busy until time 3, and cooking takes 5 minutes, the waiting time is `8 - 2 = 6`, not just `5`.
|
|
wrong_approach: "Sum cooking times and divide by n"
|
|
correct_approach: "Track chef availability and calculate finish_time - arrival for each customer"
|
|
|
|
- title: Not Handling Chef Idle Time
|
|
description: |
|
|
When a customer arrives after the chef has been idle for a while (arrival > current_time), the chef starts immediately at the arrival time, not at current_time.
|
|
|
|
For example, if the chef finishes at time 10 and the next customer arrives at time 20, cooking starts at time 20, not time 10. Use `max(current_time, arrival)` to handle both cases.
|
|
wrong_approach: "Always start cooking at current_time"
|
|
correct_approach: "Start at max(current_time, arrival)"
|
|
|
|
- title: Integer Overflow or Precision Issues
|
|
description: |
|
|
With up to `10^5` customers, each waiting up to `10^4 + 10^4` time units, the total waiting time can reach `2 * 10^9`. This fits in a 32-bit integer but leaves little margin.
|
|
|
|
More importantly, the problem requires floating-point division for the average. Ensure you're using float division, not integer division.
|
|
wrong_approach: "Integer division total_wait // n"
|
|
correct_approach: "Float division total_wait / n"
|
|
|
|
key_takeaways:
|
|
- "**Simulation pattern**: When order of processing matters, simulate the timeline step by step"
|
|
- "**Greedy works here**: Processing in arrival order is optimal because we can't reorder customers"
|
|
- "**Track state with a single variable**: The chef's availability (`current_time`) is all we need to simulate the entire queue"
|
|
- "**Waiting time = finish time - arrival time**: This formula accounts for both queue wait and cooking time"
|
|
|
|
time_complexity: "O(n). We process each customer exactly once in a single pass through the array."
|
|
space_complexity: "O(1). We only use two variables (`current_time` and `total_wait`) regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Single Pass Simulation
|
|
is_optimal: true
|
|
code: |
|
|
def average_waiting_time(customers: list[list[int]]) -> float:
|
|
current_time = 0 # When the chef becomes available
|
|
total_wait = 0 # Sum of all waiting times
|
|
|
|
for arrival, cook_time in customers:
|
|
# Chef starts when free or when customer arrives (whichever is later)
|
|
start_time = max(current_time, arrival)
|
|
# Order is ready after cooking completes
|
|
finish_time = start_time + cook_time
|
|
# Waiting time is from arrival until food is ready
|
|
total_wait += finish_time - arrival
|
|
# Update chef availability for next customer
|
|
current_time = finish_time
|
|
|
|
# Return the average waiting time
|
|
return total_wait / len(customers)
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through the customers array.
|
|
|
|
**Space Complexity:** O(1) — Only two tracking variables used.
|
|
|
|
We simulate the chef's timeline by processing customers in order. For each customer, we determine when their order finishes and calculate how long they waited from arrival. The key insight is using `max(current_time, arrival)` to handle both cases: chef busy or chef idle.
|
|
|
|
- approach_name: Explicit Timeline Simulation
|
|
is_optimal: false
|
|
code: |
|
|
def average_waiting_time(customers: list[list[int]]) -> float:
|
|
waiting_times = []
|
|
chef_free_at = 0
|
|
|
|
for arrival, cook_time in customers:
|
|
# If chef is busy, customer waits; otherwise chef waits for customer
|
|
if chef_free_at > arrival:
|
|
# Chef is busy - customer waits for chef
|
|
wait_for_chef = chef_free_at - arrival
|
|
total_wait = wait_for_chef + cook_time
|
|
chef_free_at = chef_free_at + cook_time
|
|
else:
|
|
# Chef is free - starts immediately when customer arrives
|
|
total_wait = cook_time
|
|
chef_free_at = arrival + cook_time
|
|
|
|
waiting_times.append(total_wait)
|
|
|
|
return sum(waiting_times) / len(waiting_times)
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass plus summing the list.
|
|
|
|
**Space Complexity:** O(n) — Stores individual waiting times in a list.
|
|
|
|
This version explicitly handles the two cases (chef busy vs idle) and stores each customer's waiting time. While more verbose, it makes the logic clearer for understanding. The optimal solution combines these cases with `max()` and avoids the extra list.
|