Start learning free

LESSON 279 OF 415 · BINARY SEARCH

The Factory in Swift: O(n log(k * T)) Binary Search on the Answer

Practice O(n * log(k * T)) Read the full explanation

There are N workers in a factory. Each worker packs the same type of product, and for each of them we know the time it takes them to pack one product.

Find out the minimum amount of time the workers need to pack at least K products.

Example:

Input: times = [4, 7, 3, 6, 7, 1], K = 60
Output: 30
Explanation:
1st worker packs 7 products in 4 * 7 = 28 units of time
2nd worker packs 4 products in 7 * 4 = 28 units of time
3rd worker packs 10 products in 3 * 10 = 30 units of time
4th worker packs 5 products in 6 * 5 = 30 units of time
5th worker packs 4 products in 7 * 4 = 28 units of time
6th worker packs 30 products in 1 * 30 = 30 units of time
We have 7 + 4 + 10 + 5 + 4 + 30 = 60 total products
in max(28, 28, 30, 30, 28, 30) = 30 units of time
and this is the minimum amount of time
since in 29 units of time we can pack only
7 + 4 + 9 + 4 + 4 + 29 = 57 products

Note:

Your algorithm should run in O(N * log(K * T)) time, where T is the smallest value in times, and use O(1) extra space.


STEP 1 OF 13
    Ctrl + Enter to check

    SWIFT · BINARY SEARCH

    The Factory in Swift, explained

    Problem overview

    A factory has several workers, and times[i] is the number of time units worker i needs to pack one product. They all work at the same time. The method minTime receives times and a number K and returns the smallest time in which the workers together pack at least K products.

    With times = [4, 7, 3, 6, 7, 1] and K = 60, the answer is 30. In 30 units the workers pack 7 + 4 + 10 + 5 + 4 + 30 = 60 products, while 29 units give only 57. The statement asks for O(n * log(k * T)) time, where n is the number of workers, k is K and T bounds the time per product, using O(1) extra space.

    Approach: binary search on the answer

    The simple solution tries 0, 1, 2, ... time units until enough products are packed. That works, but it may take as many steps as the answer is large. The total number of products packed never decreases as time grows. So the times split into two groups: every time below the answer packs fewer than K products, and every time from the answer onward packs at least K. A border between two groups like that can be found with binary search, here over the possible times instead of over array indices.

    The search needs a range to start from. The lowest candidate is 1. For the highest, take the fastest worker, who needs minValue units per product: working alone, they pack K products in minValue * K units, and the whole team can only do better. So the answer lies in [1, minValue * K].

    Each step tests the middle time mid. If the team packs fewer than K products by then, mid is too short and the search continues above it. Otherwise mid works, so it is saved as the best answer so far, and the search continues below it for something shorter.

    On the example the range is 1 to 60, since the fastest worker needs 1 unit. The first middle is 30, which packs exactly 60 products, so 30 is saved and the search continues in 1 to 29. Those times all pack fewer than 60, so left moves up until it passes right, and the saved 30 is returned.

    Step by step

    1. Find the fastest worker. Start var minValue at times[0] and, in a for t in times loop, update it with minValue = min(minValue, t).
    2. Set the bounds: var left = 1 and var right = minValue * K.
    3. Declare var time = 0. It keeps the smallest time found so far that packs at least K products.
    4. Loop while left <= right.
    5. Take the middle: let mid = (left + right) / 2. Integer division drops the fraction.
    6. If countProducts(times, mid) < K, then mid is too short: set left = mid + 1.
    7. Otherwise record time = mid and then set right = mid - 1 to look for a shorter time. The record comes first because the loop may finish on a mid that was too short.
    8. Return time.
    9. Write the helper countProducts(_ times: [Int], _ time: Int) -> Int above minTime. It starts var products = 0, loops with for t in times, adds time / t each time and returns products. Dividing two Ints already drops the fraction, which counts only finished products.

    The solution in Swift

    class Solution {
        func countProducts(_ times: [Int], _ time: Int) -> Int {
            var products = 0
            for t in times {
                products += time / t
            }
            return products
        }
    
        func minTime(_ times: [Int], _ K: Int) -> Int {
            var minValue = times[0]
            for t in times {
                minValue = min(minValue, t)
            }
    
            var left = 1
            var right = minValue * K
            var time = 0
            while left <= right {
                let mid = (left + right) / 2
                if countProducts(times, mid) < K {
                    left = mid + 1
                } else {
                    time = mid
                    right = mid - 1
                }
            }
            return time
        }
    }
    

    The Swift details worth a closer look:

    • The global min. min(minValue, t) is a function from the standard library that returns the smaller of two values. It works because Int is comparable. Another way to write the whole loop is times.min(), which returns an optional, because an empty array has no minimum.
    • Empty input crashes. times[0] on an empty array stops the program with an index-out-of-range error. The lesson assumes at least one worker.
    • Overflow traps. minValue * K is a 64-bit Int multiplication. It can't overflow for realistic values, but if it did, Swift would stop the program instead of wrapping around.
    • Division truncates. time / t gives the whole number of finished products, because both operands are Int.

    Complexity

    Time: O(n * log(k * T)). The search range has about minValue * K values, and the binary search halves it every iteration, so there are about log(minValue * K) iterations. Each iteration calls countProducts, which visits all n workers. Finding minValue costs O(n) once.

    Space: O(1). Only a few integers are stored.

    Edge cases and common mistakes

    • One worker. With times = [5] and K = 3 the range is 1 to 15, and the answer is 15.
    • K is 0. The upper bound is 0, the loop never runs, and the method returns 0.
    • Several equally fast workers. For [3, 3] and K = 1 the upper bound is 3, and the answer is 3. Both workers finish a product at the same time.
    • Not saving mid. Returning left or right instead of the recorded time is easy to get wrong; keep the last mid that worked.
    • Moving past a working mid. Setting left = mid + 1 when mid packs enough would skip the answer.
    • Using the slowest worker for the upper bound. That is a valid bound but a needlessly large one. The fastest worker gives a tighter bound and the correct one.

    Testing

    • times = [4, 7, 3, 6, 7, 1], K = 60 gives 30.
    • times = [5], K = 3 gives 15.
    • times = [3, 3], K = 1 gives 3.
    • times = [2, 9], K = 1 gives 2.
    • times = [3, 3], K = 0 gives 0.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free