Start learning free

LESSON 249 OF 338 · BINARY SEARCH

The Factory in Go: 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 and use O(1) extra space.


STEP 1 OF 13
    Ctrl + Enter to check

    GO · BINARY SEARCH

    The Factory in Go, explained

    Problem overview

    The function minTime receives a slice times, where times[i] is the number of time units worker i needs to pack one product, and a number k. All workers work at the same time, each at their own steady pace. The function returns the smallest amount of time in which the workers together pack at least k products.

    For 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 and 30 products, which adds up to exactly 60. In 29 units they pack only 7 + 4 + 9 + 4 + 4 + 29 = 57, which is not enough.

    The statement allows O(n * log(k * T)) time and O(1) extra space, where T is the largest per-product time. The logarithm hints that the answer itself is found by binary search.

    Approach: binary search on the answer

    It is hard to compute the best time directly, but it is easy to check a time. Given availableTime, a worker who needs t units per product packs availableTime / t products (whole products only), and the total over all workers is a simple sum.

    More time never produces fewer products. So the answer has a clean shape: times below some threshold pack fewer than k products, and every time from the threshold up packs at least k. The threshold is the answer, and binary search finds it by testing middle values and discarding half of the candidate times at each step.

    The search range needs two ends. The lower end is 1: at least one unit is needed to pack anything. The upper end is the fastest worker's time multiplied by k, because that worker alone packs k products in minValue * k units, so the true answer can't be larger.

    The search is on the answer, not on an index of the slice. Each test costs O(n) to add up the workers, and the range has about k * T values, so there are about log(k * T) tests: O(n * log(k * T)) in all.

    Step by step

    1. Find the fastest worker. Start minValue at times[0], then range over times and take the smaller of the current minimum and each workerTime.
    2. Set the search range with left, right := 1, minValue*k.
    3. Prepare answer, starting at 0. It will hold the smallest time found so far that is enough.
    4. Loop while left <= right. Go has no while, so the loop is a for with only a condition.
    5. Pick the middle time, left + (right-left)/2.
    6. Check it with the helper countProducts. If the workers pack fewer than k products in middle units, the time is too short: left = middle + 1.
    7. Otherwise the time is enough. Store it in answer before shrinking, then try for something shorter with right = middle - 1.
    8. Return answer after the loop.
    9. Write countProducts(times, availableTime). Start products at 0, range over times, and add availableTime / workerTime for each worker. Integer division drops the fraction, which is exactly "only whole products count". Return products.

    The solution in Go

    func minTime(times []int, k int) int {
    	minValue := times[0]
    	for _, workerTime := range times {
    		minValue = min(minValue, workerTime)
    	}
    	left, right := 1, minValue*k
    	answer := 0
    	for left <= right {
    		middle := left + (right-left)/2
    		if countProducts(times, middle) < k {
    			left = middle + 1
    		} else {
    			answer = middle
    			right = middle - 1
    		}
    	}
    	return answer
    }
    
    func countProducts(times []int, availableTime int) int {
    	products := 0
    	for _, workerTime := range times {
    		products += availableTime / workerTime
    	}
    	return products
    }
    

    Trace on times = [4, 7, 3, 6, 7, 1], k = 60: minValue is 1, so the range is 1 to 60. The first middle is 30, which packs 60 products, enough, so answer = 30 and the range becomes 1 to 29. The next middle is 15, which is too short, and the search continues in 16 to 29. It keeps finding middles below 30 that are too short, and the loop ends with answer still 30.

    Go details in this code

    • Built-in min. Since Go 1.21 min and max are part of the language and work on numbers without any import.
    • Integer division. availableTime / workerTime truncates toward zero. Both operands are positive here, so this is rounding down, which is the number of whole products.
    • The blank identifier. for _, workerTime := range times discards the index, because Go rejects declared variables that are unused.
    • Division by zero. A worker time of 0 would make an integer division panic at run time. The lesson's inputs have positive times.
    • Tabs or spaces. Go code is normally indented with tabs and formatted by gofmt, but the compiler accepts either.

    Complexity

    Time: O(n * log(k * T)). The search range has at most minValue * k values, so the loop runs about log₂(minValue * k) times. Each iteration calls countProducts, which visits all n workers. Finding the minimum adds one more pass of O(n).

    Space: O(1). A handful of integers.

    Edge cases and common mistakes

    • Remembering the answer. The search can finish on a middle that was too short, so the last good middle is saved in answer as it is found. Returning left or middle would be fragile.
    • One worker. For times = [5] and k = 3 the range is 1 to 15 and the answer is 15.
    • Whole products only. At 29 units the worker with time 7 packs 4 products, not 4.14. Using floating-point division would overcount.
    • Too small an upper bound. If right were smaller than the answer, the search would never see it. The fastest worker's time times k is always enough.
    • Starting the range at 0. It is harmless for correctness here but wastes a step, and 0 time packs nothing.
    • Several workers with the same time. Each is counted separately, as in the example where two workers need 7 units.
    • Large products. minValue*k and the totals are plain int values (64 bits), and only wrap around for inputs far beyond what the lesson uses.

    Testing

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