Start learning free

LESSON 285 OF 442 · BINARY SEARCH

The Factory in Go: O(N * K * T) Linear Search on Time

Practice O(n * 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 * K * T) time, where T is the smallest value in times, and use O(1) extra space.


STEP 1 OF 9
    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 how many units of time worker i needs to pack one product, and a number k. All workers pack at the same time. The function returns the smallest amount of time after which the workers have packed at least k products in total.

    In the example, times = [4, 7, 3, 6, 7, 1] and k = 60. In 30 units of time the workers pack 7, 4, 10, 5, 4 and 30 products, which is exactly 60. In 29 units they pack only 7 + 4 + 9 + 4 + 4 + 29 = 57. So the answer is 30.

    The statement allows O(N * K * T) time, where N is the number of workers and T is the smallest value in times, and O(1) extra space.

    Approach: try 0, 1, 2, ... units of time

    A worker who needs x units per product finishes t / x products (rounded down) in t units of time, because only complete products count. Adding this up over all workers gives the total for a given t.

    That total never decreases as t grows: more time can only mean the same number of products or more. This is what makes a simple search correct. Start at t = 0 and increase it by one until the total reaches k. The first t that works is the smallest, because every smaller value was tried and failed.

    How long can the loop run? The fastest worker, whose time per product is T, alone packs k products in T * k units. So the answer is at most T * k, and the loop makes at most T * k + 1 checks. Each check adds up N numbers, which gives the O(N * K * T) bound from the statement.

    This is slow when the answer is large, but it is simple and obviously correct. Because the total only goes up with t, the search can be done with binary search on the answer instead of one step at a time. The second lesson on this problem does that.

    Step by step

    1. Declare the time to try. t := 0 is the candidate answer. It is named t because time is the name of a standard library package.
    2. Loop while the workers are too slow. for countProducts(times, t) < k keeps going as long as t units are not enough. The helper doesn't exist yet; assuming it lets you write the main logic first.
    3. Try one more unit. t++ inside the loop.
    4. Return t. When the loop stops, t is the first time for which the total reached k.
    5. Declare the helper. countProducts(times []int, t int) int returns the total after t units.
    6. Start a total. products := 0.
    7. Loop over the workers. for _, x := range times gives each worker's time per product as x. The blank identifier _ discards the index, which isn't needed.
    8. Add one worker's products. products += t / x. Dividing two integers in Go already drops the fraction, which is exactly "complete products only".
    9. Return the total.

    The solution in Go

    func countProducts(times []int, t int) int {
    	products := 0
    	for _, x := range times {
    		products += t / x
    	}
    	return products
    }
    
    func minTime(times []int, k int) int {
    	t := 0
    	for countProducts(times, t) < k {
    		t++
    	}
    	return t
    }
    

    The Go details worth knowing:

    • Integer division truncates. With both operands of type int, t / x is the integer part of the quotient. A worker with x = 7 packs 29 / 7 = 4 products in 29 units and 30 / 7 = 4 in 30. This is the reason the answer can be larger than the exact fractional answer.
    • Division by zero panics. If some value in times were 0, the expression t / x would stop the program with a run-time panic. The lesson's workers always need at least one unit per product.
    • for as a while. for countProducts(times, t) < k { t++ } has only a condition. Go has no while keyword. The condition is re-evaluated before every pass, so the body runs zero times if the first check already succeeds.
    • range over a slice. It yields an index and a value. Go won't compile an unused variable, so the index is named _ when you don't need it.
    • Slices are cheap to pass. times is passed to the helper as a small header pointing at the same data. Nothing is copied, and the helper only reads it.

    Complexity

    Time: O(N * K * T). One call to countProducts takes O(N). The loop calls it for t = 0, 1, 2, ... up to the answer, which is at most T * K. Multiplying gives N * K * T.

    Space: O(1). Two integers in minTime and two in the helper. Nothing grows with the input.

    Edge cases and common mistakes

    • One worker. With times = [5] and k = 3 the answer is 15: at t = 14 the worker has packed 2 products, at 15 they have packed 3. With k = 1 it is 5.
    • k = 0. Zero products are packed already at t = 0, so the loop never runs and the function returns 0.
    • Exactly at a boundary. The condition is < k, not <= k. "At least k" means a total equal to k is enough to stop. With <= the loop would overshoot by one unit.
    • Leftover time is wasted. In the example, 29 units give the 7-unit worker only 4 products, with 1 unit unused. The sum is exact only when time divides evenly, so you can't compute the answer as k divided by the total speed.
    • Different workers finish different counts. The answer isn't the average of anything. Each worker is counted separately and the totals are added.
    • Huge answers. With times = [1000000] and k = 100, the answer is 100000000. The loop runs a hundred million times, which is the reason a faster search matters when the numbers are large.

    Testing

    • times = [4, 7, 3, 6, 7, 1], k = 60 gives 30.
    • times = [2, 3], k = 5 gives 6: at t = 6 the workers pack 3 + 2 = 5.
    • times = [5], k = 3 gives 15.
    • times = [4, 4, 4], k = 0 gives 0.

    The helper alone is also easy to probe: countProducts([]int{2, 3}, t) returns 0, 0, 1, 2, 3 for t from 0 to 4.

    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free