2594. Minimum Time to Repair Cars
Problem Description
You have an array ranks
where each element represents the rank of a mechanic. A mechanic with rank r
needs r * n²
minutes to repair n
cars.
Given:
ranks
: an integer array whereranks[i]
is the rank of the i-th mechaniccars
: the total number of cars that need to be repaired
The key points are:
- Each mechanic works independently and simultaneously
- A mechanic with rank
r
takesr * n²
minutes to repair exactlyn
cars - For example, a mechanic with rank 3 would take:
- 3 × 1² = 3 minutes to repair 1 car
- 3 × 2² = 12 minutes to repair 2 cars
- 3 × 3² = 27 minutes to repair 3 cars
You need to find the minimum time required to repair all cars
when all mechanics work at the same time. Each mechanic can repair any number of cars (up to the total needed), and you want to distribute the work optimally to minimize the total time.
The solution uses binary search on the time. For a given time t
, we can calculate how many cars each mechanic can repair: if a mechanic has rank r
, they can repair ⌊√(t/r)⌋
cars in time t
. We search for the minimum time where the total number of cars that can be repaired is at least equal to the required number of cars.
Intuition
The key insight is recognizing that this is a monotonic problem - as we give mechanics more time, they can repair more cars. This monotonic property makes binary search a natural fit.
Let's think about it differently: instead of asking "how should we distribute cars to minimize time?", we can ask "given a fixed amount of time t
, can we repair all the cars?" This transforms the optimization problem into a decision problem.
For any given time t
, we can calculate exactly how many cars each mechanic can complete. Since a mechanic with rank r
needs r * n²
minutes to repair n
cars, we can rearrange this formula: if we have t
minutes available, then t = r * n²
, which means n = √(t/r)
. So each mechanic can repair ⌊√(t/r)⌋
cars in time t
.
The beautiful part is that we don't need to figure out the optimal distribution of cars among mechanics. We just need to check if the total capacity (sum of all cars that can be repaired by all mechanics in time t
) meets our requirement. If it does, we might be able to do it faster (try a smaller time). If it doesn't, we definitely need more time.
This naturally leads to binary search on the time:
- If mechanics can repair at least
cars
vehicles in timet
, try a smaller time - If they can't repair enough in time
t
, we need more time
The search space for time ranges from 0
to some upper bound. The worst case would be if we only had one mechanic with the highest rank repairing all cars sequentially, giving us an upper bound of ranks[0] * cars * cars
.
Learn more about Binary Search patterns.
Solution Approach
The implementation uses binary search with a helper function to check feasibility at each time point.
Binary Search Setup:
- We use
bisect_left
to find the leftmost (minimum) time where the condition is satisfied - The search range is
range(ranks[0] * cars * cars)
, which creates values from0
toranks[0] * cars * cars - 1
- The upper bound
ranks[0] * cars * cars
represents the worst-case scenario where the first mechanic (potentially with highest rank) repairs all cars alone
Check Function:
The helper function check(t)
determines if all cars can be repaired within time t
:
- For each mechanic with rank
r
, calculate how many cars they can repair:⌊√(t/r)⌋
- We use
int(sqrt(t // r))
to compute this valuet // r
performs integer divisionsqrt()
calculates the square rootint()
floors the result to get whole cars
- Sum up all cars that can be repaired by all mechanics
- Return
True
if the total is at leastcars
, otherwiseFalse
How Binary Search Works Here:
bisect_left
searches for the first position wherecheck(t)
returnsTrue
- It uses the
key
parameter to transform each time value through thecheck
function - When
check(t)
returnsFalse
, it means we need more time - When
check(t)
returnsTrue
, it means this time is sufficient, but we try to find a smaller one - The algorithm converges to the minimum time where exactly
cars
or more can be repaired
Time Complexity:
- Binary search runs in
O(log(ranks[0] * cars²))
iterations - Each check function iterates through all mechanics:
O(len(ranks))
- Overall:
O(len(ranks) * log(ranks[0] * cars²))
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with ranks = [4, 2, 3, 1]
and cars = 10
.
Step 1: Set up binary search range
- Lower bound: 0
- Upper bound:
4 × 10 × 10 = 400
(using first mechanic's rank) - We'll search for the minimum time in range [0, 400)
Step 2: Binary search process
Let's trace through a few iterations:
Check time = 200 (midpoint)
- Mechanic rank 4: Can repair
⌊√(200/4)⌋ = ⌊√50⌋ = 7
cars - Mechanic rank 2: Can repair
⌊√(200/2)⌋ = ⌊√100⌋ = 10
cars - Mechanic rank 3: Can repair
⌊√(200/3)⌋ = ⌊√66.67⌋ = 8
cars - Mechanic rank 1: Can repair
⌊√(200/1)⌋ = ⌊√200⌋ = 14
cars - Total: 7 + 10 + 8 + 14 = 39 cars ✓ (more than 10 needed)
- Since we can repair all cars, try a smaller time
Check time = 100
- Mechanic rank 4: Can repair
⌊√(100/4)⌋ = ⌊√25⌋ = 5
cars - Mechanic rank 2: Can repair
⌊√(100/2)⌋ = ⌊√50⌋ = 7
cars - Mechanic rank 3: Can repair
⌊√(100/3)⌋ = ⌊√33.33⌋ = 5
cars - Mechanic rank 1: Can repair
⌊√(100/1)⌋ = ⌊√100⌋ = 10
cars - Total: 5 + 7 + 5 + 10 = 27 cars ✓ (still more than 10)
- Try even smaller time
Check time = 50
- Mechanic rank 4: Can repair
⌊√(50/4)⌋ = ⌊√12.5⌋ = 3
cars - Mechanic rank 2: Can repair
⌊√(50/2)⌋ = ⌊√25⌋ = 5
cars - Mechanic rank 3: Can repair
⌊√(50/3)⌋ = ⌊√16.67⌋ = 4
cars - Mechanic rank 1: Can repair
⌊√(50/1)⌋ = ⌊√50⌋ = 7
cars - Total: 3 + 5 + 4 + 7 = 19 cars ✓ (still sufficient)
Check time = 25
- Mechanic rank 4: Can repair
⌊√(25/4)⌋ = ⌊√6.25⌋ = 2
cars - Mechanic rank 2: Can repair
⌊√(25/2)⌋ = ⌊√12.5⌋ = 3
cars - Mechanic rank 3: Can repair
⌊√(25/3)⌋ = ⌊√8.33⌋ = 2
cars - Mechanic rank 1: Can repair
⌊√(25/1)⌋ = ⌊√25⌋ = 5
cars - Total: 2 + 3 + 2 + 5 = 12 cars ✓ (just enough!)
Check time = 12
- Mechanic rank 4: Can repair
⌊√(12/4)⌋ = ⌊√3⌋ = 1
car - Mechanic rank 2: Can repair
⌊√(12/2)⌋ = ⌊√6⌋ = 2
cars - Mechanic rank 3: Can repair
⌊√(12/3)⌋ = ⌊√4⌋ = 2
cars - Mechanic rank 1: Can repair
⌊√(12/1)⌋ = ⌊√12⌋ = 3
cars - Total: 1 + 2 + 2 + 3 = 8 cars ✗ (not enough)
- Need more time
Check time = 16
- Mechanic rank 4: Can repair
⌊√(16/4)⌋ = ⌊√4⌋ = 2
cars - Mechanic rank 2: Can repair
⌊√(16/2)⌋ = ⌊√8⌋ = 2
cars - Mechanic rank 3: Can repair
⌊√(16/3)⌋ = ⌊√5.33⌋ = 2
cars - Mechanic rank 1: Can repair
⌊√(16/1)⌋ = ⌊√16⌋ = 4
cars - Total: 2 + 2 + 2 + 4 = 10 cars ✓ (exactly enough!)
The binary search converges to 16 minutes as the minimum time needed to repair all 10 cars.
Verification: With 16 minutes, the optimal distribution would be:
- Mechanic rank 1 repairs 4 cars (takes 1×4² = 16 minutes)
- Mechanic rank 2 repairs 2 cars (takes 2×2² = 8 minutes)
- Mechanic rank 3 repairs 2 cars (takes 3×2² = 12 minutes)
- Mechanic rank 4 repairs 2 cars (takes 4×2² = 16 minutes)
- Total: 4 + 2 + 2 + 2 = 10 cars ✓
Solution Implementation
1from typing import List
2from math import sqrt
3from bisect import bisect_left
4
5class Solution:
6 def repairCars(self, ranks: List[int], cars: int) -> int:
7 """
8 Find the minimum time required to repair a given number of cars.
9
10 Args:
11 ranks: List of integers representing mechanic ranks (efficiency)
12 cars: Total number of cars to repair
13
14 Returns:
15 Minimum time required to repair all cars
16 """
17
18 def can_repair_all_cars(time_limit: int) -> bool:
19 """
20 Check if all cars can be repaired within the given time limit.
21
22 For a mechanic with rank r, they can repair n cars in r * n^2 time.
23 Given time t, they can repair floor(sqrt(t/r)) cars.
24 """
25 total_cars_repaired = 0
26
27 for rank in ranks:
28 # Calculate how many cars this mechanic can repair in given time
29 # From equation: time = rank * cars^2
30 # Solving for cars: cars = sqrt(time / rank)
31 cars_by_this_mechanic = int(sqrt(time_limit // rank))
32 total_cars_repaired += cars_by_this_mechanic
33
34 # Check if we can repair at least the required number of cars
35 return total_cars_repaired >= cars
36
37 # Binary search for the minimum time
38 # Upper bound: worst case is the best mechanic repairs all cars alone
39 min_rank = min(ranks)
40 max_time = min_rank * cars * cars
41
42 # Find the leftmost position where can_repair_all_cars returns True
43 minimum_time = bisect_left(
44 range(max_time + 1), # Search space from 0 to max_time
45 True, # Looking for True value
46 key=can_repair_all_cars # Transform each time value using this function
47 )
48
49 return minimum_time
50
1class Solution {
2 /**
3 * Finds the minimum time required for mechanics to repair all cars.
4 * Each mechanic with rank r takes r * n^2 time to repair n cars.
5 *
6 * @param ranks Array containing the rank of each mechanic
7 * @param cars Total number of cars to be repaired
8 * @return Minimum time required to repair all cars
9 */
10 public long repairCars(int[] ranks, int cars) {
11 // Binary search boundaries
12 // Lower bound: minimum possible time (0)
13 long minTime = 0;
14 // Upper bound: worst case where one mechanic repairs all cars
15 // Time = rank * cars^2, using the best mechanic (smallest rank)
16 long maxTime = (long) ranks[0] * cars * cars;
17
18 // Binary search for the minimum time
19 while (minTime < maxTime) {
20 // Calculate middle point to avoid overflow
21 long midTime = (minTime + maxTime) >> 1;
22
23 // Count total cars that can be repaired in midTime
24 long totalCarsRepaired = 0;
25 for (int rank : ranks) {
26 // For a mechanic with rank r, if they work for time t,
27 // they can repair sqrt(t/r) cars (derived from t = r * n^2)
28 totalCarsRepaired += (long) Math.sqrt(midTime / rank);
29 }
30
31 // Check if we can repair enough cars in midTime
32 if (totalCarsRepaired >= cars) {
33 // Can repair all cars, try to find a smaller time
34 maxTime = midTime;
35 } else {
36 // Cannot repair enough cars, need more time
37 minTime = midTime + 1;
38 }
39 }
40
41 return minTime;
42 }
43}
44
1class Solution {
2public:
3 long long repairCars(vector<int>& ranks, int cars) {
4 // Binary search bounds: minimum time is 0, maximum time is when the best mechanic repairs all cars
5 // Time formula: time = rank * n^2, where n is number of cars repaired
6 // So max time = min_rank * cars^2
7 long long left = 0;
8 long long right = 1LL * ranks[0] * cars * cars;
9
10 // Binary search to find minimum time needed
11 while (left < right) {
12 // Calculate middle point
13 long long mid = (left + right) >> 1;
14
15 // Count total cars that can be repaired within 'mid' time
16 long long totalCarsRepaired = 0;
17 for (int rank : ranks) {
18 // For each mechanic, calculate how many cars they can repair in 'mid' time
19 // From time = rank * n^2, we get n = sqrt(time / rank)
20 totalCarsRepaired += sqrt(mid / rank);
21 }
22
23 // Check if we can repair enough cars in 'mid' time
24 if (totalCarsRepaired >= cars) {
25 // If yes, try to find a smaller time
26 right = mid;
27 } else {
28 // If no, we need more time
29 left = mid + 1;
30 }
31 }
32
33 // Return the minimum time needed to repair all cars
34 return left;
35 }
36};
37
1/**
2 * Calculates the minimum time required for mechanics to repair all cars
3 * @param ranks - Array of integers representing the rank (efficiency) of each mechanic
4 * @param cars - Total number of cars to be repaired
5 * @returns Minimum time required to repair all cars
6 */
7function repairCars(ranks: number[], cars: number): number {
8 // Binary search bounds
9 // Left bound: minimum possible time (0)
10 let left: number = 0;
11 // Right bound: worst case scenario - slowest mechanic repairs all cars
12 // Time formula: rank * n^2 where n is number of cars
13 let right: number = ranks[0] * cars * cars;
14
15 // Binary search to find minimum time
16 while (left < right) {
17 // Calculate midpoint to avoid overflow
18 const mid: number = left + Math.floor((right - left) / 2);
19
20 // Count total cars that can be repaired within 'mid' time
21 let totalCarsRepaired: number = 0;
22 for (const rank of ranks) {
23 // For each mechanic, calculate how many cars they can repair
24 // Formula derived from: time = rank * n^2, solving for n: n = sqrt(time/rank)
25 totalCarsRepaired += Math.floor(Math.sqrt(mid / rank));
26 }
27
28 // Check if we can repair enough cars within 'mid' time
29 if (totalCarsRepaired >= cars) {
30 // If yes, try to find a smaller time
31 right = mid;
32 } else {
33 // If no, we need more time
34 left = mid + 1;
35 }
36 }
37
38 // Return the minimum time required
39 return left;
40}
41
Time and Space Complexity
The time complexity is O(n × log M)
, where:
n
is the length of theranks
list (number of mechanics)M
is the upper bound of the binary search, which isranks[0] * cars * cars
The binary search performed by bisect_left
runs in O(log M)
time. For each iteration of the binary search, the check
function is called, which iterates through all n
elements in ranks
to compute the sum, taking O(n)
time. Therefore, the overall time complexity is O(n × log M)
.
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space. The check
function uses a generator expression that doesn't create an intermediate list, and only maintains variables for the sum calculation and the binary search bounds.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Square Root Calculation
When calculating sqrt(time_limit // rank)
, if time_limit
is very large and rank
is very small, the division time_limit // rank
might overflow or cause precision issues. Additionally, using integer division (//
) before taking the square root can lead to incorrect results.
Problem Example:
# Incorrect approach
cars_by_this_mechanic = int(sqrt(time_limit // rank))
If time_limit = 10
and rank = 3
, then 10 // 3 = 3
, and sqrt(3) ≈ 1.73
, giving us 1
car. However, with time 10 and rank 3, we should be able to repair more cars since 3 * 1² = 3
and 3 * 2² = 12
, so only 1 car is correct but the calculation path could be misleading with larger numbers.
Solution: Use floating-point division to maintain precision:
cars_by_this_mechanic = int(sqrt(time_limit / rank))
2. Incorrect Binary Search Bounds
The upper bound calculation uses min(ranks) * cars * cars
, but this assumes the most efficient mechanic repairs all cars. If you accidentally use max(ranks)
or ranks[0]
without sorting, you might set the bound too low or inconsistently.
Problem Example:
# Incorrect - using first element without considering it might not be minimum max_time = ranks[0] * cars * cars
Solution: Always use the minimum rank for the upper bound or a sufficiently large value:
min_rank = min(ranks)
max_time = min_rank * cars * cars
3. Off-by-One Error in Binary Search Range
Using range(max_time)
instead of range(max_time + 1)
would exclude the maximum time value from the search space, potentially missing the correct answer if the minimum time equals max_time
.
Problem Example:
# Incorrect - excludes max_time from search
minimum_time = bisect_left(range(max_time), True, key=can_repair_all_cars)
Solution: Include the upper bound in the search range:
minimum_time = bisect_left(range(max_time + 1), True, key=can_repair_all_cars)
4. Early Termination in Check Function
Adding an optimization to break early when enough cars are repaired might seem efficient but could lead to issues if you need to track exact distributions later.
Problem Example:
def can_repair_all_cars(time_limit: int) -> bool:
total_cars_repaired = 0
for rank in ranks:
cars_by_this_mechanic = int(sqrt(time_limit / rank))
total_cars_repaired += cars_by_this_mechanic
if total_cars_repaired >= cars: # Early termination
return True # Might miss calculating exact distribution
return False
Solution: Keep the full calculation unless performance is critical and you're certain about not needing complete information:
def can_repair_all_cars(time_limit: int) -> bool:
total_cars_repaired = sum(int(sqrt(time_limit / rank)) for rank in ranks)
return total_cars_repaired >= cars
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!