Facebook Pixel

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 where ranks[i] is the rank of the i-th mechanic
  • cars: 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 takes r * n² minutes to repair exactly n 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 time t, 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 from 0 to ranks[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 value
    • t // r performs integer division
    • sqrt() calculates the square root
    • int() 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 least cars, otherwise False

How Binary Search Works Here:

  1. bisect_left searches for the first position where check(t) returns True
  2. It uses the key parameter to transform each time value through the check function
  3. When check(t) returns False, it means we need more time
  4. When check(t) returns True, it means this time is sufficient, but we try to find a smaller one
  5. 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 Evaluator

Example 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 the ranks list (number of mechanics)
  • M is the upper bound of the binary search, which is ranks[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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More