DSA sheet · Binary Search · Binary search on answer (max of min)

Aggressive Cows

This is the classic "maximise the minimum distance" problem, and the teacher says it is asked a lot in interviews. We are given stall positions and k cows, and we must spread the cows out so that the two closest cows are as far apart as possible. She first builds a slow but simple solution that tries every possible gap one by one (the brute force), shows why it gives TLE, and then replaces that loop with binary search on the answer.

Why it matters: once you understand this page, Magnetic Force Between Two Balls is the same code, and the "minimise the maximum" problems (Allocate Pages, Split Array) are its mirror image. The skill you learn here is how to pick the answer range (low, high), write a yes/no check, and decide which half to keep.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Normal binary search in one paragraph

Binary search works on something sorted, or more generally on anything where a yes/no question changes its answer only once as you move from left to right. We keep two ends, low and high, look at the middle mid, and from that one look we can throw away half of what's left. Each step halves the search space, so about log₂(size) steps are enough.

How to compute mid, and why that form

the safe way to find the middle
mid = low + (high - low) // 2

Mathematically this equals (low + high) // 2. The teacher always writes the first form because in Java or C++, low + high can go past the biggest int (about 2.1 × 10⁹) when both are large, and the result becomes a wrong, negative number. high - low is always small enough, so the safe form never overflows. In Python, integers never overflow, so both forms are fine. We still write the safe form so the habit carries over to other languages.

The pattern: binary search on the answer

In normal binary search we search inside the array. In this pattern we search over all possible answers. Three things make it work:

  1. The answer range. Find the smallest answer that could ever make sense (low) and the biggest (high). The real answer is somewhere on this number line.
  2. A yes/no check. Write a function that takes one candidate answer and says "possible" or "not possible". It does not find the answer. It only tests one value.
  3. Monotonic means the yes/no changes only once. On the number line, all the "yes" values sit on one side and all the "no" values on the other. That is exactly what binary search needs: look at mid, and you know which side the boundary is on.

How do we spot this pattern? A strong hint is that the answer is a number that is not taken from the array (here, a distance), and the question says "maximum of the minimum" or "minimum of the maximum".

Maximise the minimum vs minimise the maximum

maximise the minimum (this page)minimise the maximum (Allocate Pages, Split Array)
we choosea minimum gap we insist ona maximum load we allow
small candidateeasy → yestoo tight → no
big candidatetoo demanding → noeasy → yes
the line looks likeY Y Y Y N N NN N N Y Y Y Y
we wantthe last yesthe first yes
when check(mid) is yessave it, go right: low = mid + 1save it, go left: high = mid - 1
when check(mid) is nogo left: high = mid - 1go right: low = mid + 1

Part A · Brute force: try every gap one by one

GeeksforGeeks · Aggressive Cows

1The question in simple words

You get an array stalls. Each number is the position of a stall on a straight line (like house numbers on a road). All positions are different. You also get k, the number of cows. The cows are aggressive: if two of them are close, they fight. So we want to put the k cows into k different stalls so that the smallest distance between any two cows is as large as possible. Return that largest possible smallest distance.

Example: stalls = [1, 2, 4, 8, 9], k = 3.

index01234
position12489
road:   1  2  .  4  .  .  .  8  9
good:   C        C           C         gaps 3 and 4 → smallest = 3
bad:    C  C     C                     gaps 1 and 2 → smallest = 1

Putting cows at 1, 4, 8 gives distances 3 and 4. The closest pair is 3 apart. You can't do better, so the answer is 3.

Putting them at 1, 2, 4 is allowed (all three fit), but the closest pair is only 1 apart. The question isn't "can they fit", it's "fit them as far apart as possible, and report the smallest distance". That's what "maximise the minimum" means: for one placement, take its minimum gap; across all placements, pick the largest of those minimums.

2What the constraints tell us

3Intuition

Turn the question around. Instead of "where should the cows go?", ask: "if I insist on a minimum gap of g, can I still fit all k cows?" That question is easy to answer: walk along the road and drop a cow at the first stall that is at least g away from the previous cow.

Now try g = 1, 2, 3, … one by one. Small gaps are easy to satisfy. As g grows, it gets harder, and at some point the cows no longer fit. The biggest g that still works is our answer.

4Building the logic from examples

The teacher tries each gap on [1, 2, 4, 8, 9], k = 3

Gaps 1, 2 and 3 all work. 4 doesn't. The question wants the largest working gap → 3.

The yes/no line over every possible gap

gap12345678
all fit?YYYNNNNNanswer = the last Y = 3

Notice the shape: all yes, then all no. It flips only once. That's no accident:

Doubt: why can the line never go Y, N, Y?
→ If a gap g works, take that exact same placement. Every pair of cows is at least g apart, so it's also at least g−1 apart. So every smaller gap works too. And if g fails, every bigger gap is even stricter, so it fails too. One flip, from yes to no.

Choosing low: why start at 1?

A gap of 0 would mean two cows standing on the same stall, which isn't allowed (one cow per stall, and all positions are different). So the smallest distance that can ever happen between two different stalls is at least 1. low = 1.

Choosing high: why last − first?

The teacher could have used the biggest value the constraints allow, but she wants the tightest range. Think of the easiest case, k = 2 (the minimum the constraints allow). With only two cows, you'd put one at the first stall and one at the last stall. The gap is then 9 − 1 = 8. With more cows, the closest pair can only get closer. So no answer can ever be bigger than last − first.

For 2 cows on our road: (1, 2) gives 1, (1, 4) gives 3, (1, 8) gives 7, (1, 9) gives 8. The corners win. high = stalls[-1] - stalls[0].

Doubt: why do we sort the stalls first?
→ Two reasons. (1) high = last − first only means "biggest minus smallest" if the array is sorted. If the input were [1, 9, 4], then last − first = 3, which is wrong. The real biggest distance is 9 − 1 = 8. (2) The check walks left to right and only compares each stall with the previous cow. That's correct only if the stalls are in road order. The teacher leaves "why is sorting needed?" as homework at the end of the video. This is the answer.

The check function: canPlace(stalls, k, gap)

Doubt 1: why compare with the most recent cow and not the first cow?
→ The closest cow to the new stall is the one placed most recently. When we look at stall 8 with cows at 1 and 4, the distance that matters is 8 − 4 = 4, not 8 − 1 = 7. That's why last is updated every time a cow is placed.
Doubt 2: is it safe to always put the first cow in the first stall, and to place each cow as early as possible?
→ Yes. Placing a cow earlier leaves more road for the cows after it. Any valid placement could be shifted so its first cow sits at the first stall, and each later cow at the earliest stall far enough away, without breaking anything. So if this greedy walk can't fit k cows, no placement can.
Doubt 3: why >= and not >?
→ The gap means "at least this far". At gap 3, stall 4 is exactly 3 from stall 1, and the teacher places a cow there. Using > would wrongly reject it.

Going from low to high: don't return on the first yes!

While writing the loop, the teacher stops and asks: if we go from gap 1 upward and return as soon as the check says yes, what happens? Gap 1 says yes right away, and we'd return 1. That's wrong, because 2 and 3 also work and we want the largest. So there are two correct ways:

5Approach steps

  1. Sort the stalls.
  2. low = 1, high = stalls[-1] - stalls[0].
  3. For each gap from low to high: if canPlace says yes, save it as the answer; otherwise stop.
  4. Return the saved answer.

6Code (Python)

Aggressive Cows, brute force (low → high)
class Solution:
    def aggressiveCows(self, stalls, k):
        stalls.sort()
        low = 1
        high = stalls[-1] - stalls[0]
        answer = 0
        for gap in range(low, high + 1):
            if self.canPlace(stalls, k, gap):
                answer = gap        # works, but a bigger one might too
            else:
                break               # first "no": every bigger gap fails too
        return answer

    def canPlace(self, stalls, k, gap):
        cows = 1                    # first cow in the first stall
        last = stalls[0]            # position of the most recent cow
        for i in range(1, len(stalls)):
            if stalls[i] - last >= gap:
                cows += 1
                last = stalls[i]
        return cows >= k
the other direction (high → low): first yes is the answer
class Solution:
    def aggressiveCows(self, stalls, k):
        stalls.sort()
        for gap in range(stalls[-1] - stalls[0], 0, -1):
            if self.canPlace(stalls, k, gap):
                return gap          # the first yes from the top is the largest
        return 1

    def canPlace(self, stalls, k, gap):
        cows, last = 1, stalls[0]
        for i in range(1, len(stalls)):
            if stalls[i] - last >= gap:
                cows += 1
                last = stalls[i]
        return cows >= k

7Code line by line

linewhat it means
stalls.sort()Put the stalls in road order, so last − first is the full length and the greedy walk works.
low = 1Two cows can never share a stall, so the gap is at least 1.
high = stalls[-1] - stalls[0]The best case, 2 cows at the two ends. No answer can beat this.
for gap in range(low, high + 1):Try every possible gap, smallest first. + 1 because Python's range stops before its end.
answer = gapThis gap works. Remember it, but keep looking for a bigger one.
breakThe first failure. Bigger gaps are stricter, so they all fail too.
cows = 1 last = stalls[0]Inside the check: the first cow goes in the first stall.
if stalls[i] - last >= gap:Is this stall far enough from the most recent cow?
cows += 1 last = stalls[i]Yes → place a cow here. Future stalls are now measured from this one.
return cows >= kDid we fit at least k cows with this gap?

8Dry run (hand table)

stalls = [1, 2, 4, 8, 9], k = 3. low = 1, high = 8.

gapcows placed atcountcheckanswer after
11, 2, 4, 8, 95yes1
21, 4, 83yes2
31, 4, 83yes3
41, 82no → break3

Return 3 ✓. (With gap 1 the walk places 5 cows, more than k. That still counts as yes.)

9Complexity & remember

Which loop can we shrink? The teacher's reasoning: the inner walk over the stalls can't be skipped, because we must look at each stall to decide whether a cow goes there. But the outer loop runs over the numbers 1, 2, 3, …, high, which are already sorted, and the yes/no answers flip only once. That is a perfect place for binary search.

Remember brute forceSort → low = 1, high = last − first → try each gap; canPlace = greedy walk with last; save on yes, stop on the first no. Correct but O((max−min) · n).

Part B · Optimal: binary search on the gap

1The question

Same as Part A. Same low, same high, same canPlace. The only change: instead of walking the gaps one at a time, jump to the middle gap and throw away half the range each time.

2Constraints

Same as Part A. Now the outer part costs about log₂(10⁸) ≈ 27 checks instead of 10⁸.

3Intuition

The gaps 1 … high form a sorted number line, and the check gives Y Y Y … Y N N … N. We want the last Y. Pick the middle gap:

4Building the logic

Doubt 1: why save mid in answer instead of returning it?
→ A yes at mid only tells us mid works. We don't know yet if it's the largest that works. We save it in case nothing bigger works, then keep searching on the right. If something bigger works, it overwrites the saved value.
Doubt 2: why low <= high and not low < high?
→ When low == high there is still one gap we haven't tested. In our dry run below, the answer 3 is found exactly in the step where low = high = 3. With < we'd skip it.
Doubt 3: what should answer start as?
→ The teacher sets it to 0 (she says −1 works too) because the problem guarantees an answer exists. Since k ≤ n and all positions differ, gap 1 always works, so answer always gets overwritten.

The answer range shrinking (stalls [1, 2, 4, 8, 9], k = 3)

gap12345678
step 112345678mid 4 → no → drop 4…8
step 212345678mid 2 → yes → save 2, drop 1…2
step 312345678mid 3 → yes → save 3, low = 4 > high = 3, stop

5Approach steps

  1. Sort the stalls. low = 1, high = stalls[-1] - stalls[0], answer = 0.
  2. While low <= high: mid = low + (high - low) // 2.
  3. If canPlace(stalls, k, mid): answer = mid, low = mid + 1.
  4. Else: high = mid - 1.
  5. Return answer.

6Code (Python)

Aggressive Cows, binary search on the answer
class Solution:
    def aggressiveCows(self, stalls, k):
        stalls.sort()
        n = len(stalls)
        low = 1
        high = stalls[n - 1] - stalls[0]
        answer = 0
        while low <= high:
            mid = low + (high - low) // 2      # a candidate minimum gap
            if self.canPlace(stalls, k, mid):
                answer = mid                   # works: save it...
                low = mid + 1                  # ...and try a bigger gap
            else:
                high = mid - 1                 # too big: try smaller gaps
        return answer

    def canPlace(self, stalls, k, gap):
        cows = 1
        last = stalls[0]
        for i in range(1, len(stalls)):
            if stalls[i] - last >= gap:
                cows += 1
                last = stalls[i]
        return cows >= k

Optional speed-up: inside canPlace you can return True as soon as cows == k. It doesn't change the big-O, it just stops early.

7Code line by line

linewhat it means
low = 1 high = stalls[n - 1] - stalls[0]The answer range, the same as in the brute force.
answer = 0The best gap found so far. It gets overwritten, because gap 1 always works.
while low <= high:While there are untested gaps left in the range.
mid = low + (high - low) // 2The middle candidate gap (the overflow-safe form).
if self.canPlace(stalls, k, mid):Can all k cows fit when they must be at least mid apart?
answer = mid low = mid + 1Yes → mid is possible. We want the maximum, so search the bigger gaps.
high = mid - 1No → mid is too demanding, and so is everything bigger. Search the smaller gaps.
return answerThe last gap that said yes, which is the largest one.

8Dry run (hand table)

stalls = [1, 2, 4, 8, 9] (already sorted), k = 3.

steplowhighmidcows placed atcheck(mid)decisionthrown away
11841, 8no (2 < 3)high = 3gaps 4…8
21321, 4, 8yesanswer = 2, low = 3gaps 1…2
33331, 4, 8yesanswer = 3, low = 4gap 3 (done)
end43low > high → stop → return 3 ✓

Only 3 checks instead of 4 here. With high = 10⁸, it's about 27 checks instead of 10⁸.

9Complexity & remember

Remember the optimal waySame check, same range. Replace the for loop with binary search. Yes → save and go right (we want bigger). No → go left.

Part C · Revision page

A · brute forceB · binary search on answer
rangelow = 1, high = last − first (after sorting)
checkgreedy: first cow at the first stall, the next one wherever stalls[i] − last ≥ gap; yes if cows ≥ k
how gaps are tried1, 2, 3, … until the first nomid, then half the range each time
on yessave, continuesave, low = mid + 1
on nobreakhigh = mid - 1
timeO(n · (max−min)) ≈ 10¹⁴O(n log(max−min)) ≈ 2.7 × 10⁷
maximise the minimum (cows, balls)minimise the maximum (pages, split array)
yes/no lineY Y Y N NN N Y Y Y
wantlast yesfirst yes
yes →low = mid + 1high = mid - 1
If you remember only 5 lines 1. Ask "can all k cows fit if they must be at least g apart?" That's a yes/no check.
2. Small gaps say yes, big gaps say no, and it flips only once → binary search over g.
3. low = 1 (no sharing stalls), high = last − first (2 cows at the two ends). Sort first!
4. Check: first cow at stalls[0], place the next one when stalls[i] − last ≥ g, update last.
5. Yes → answer = mid, go right. No → go left. Return answer.
Mistakes to avoid ✗ forgetting to sort (high and the greedy walk both break)
✗ comparing with the first cow instead of the most recent one (last not updated)
✗ using > instead of >= in the gap test
✗ returning the first yes when scanning from low to high (that gives 1)
✗ moving the wrong way: on yes we go right here, because we want the maximum
✗ while low < high (skips the last candidate)
test it yourself (paste under any of the solutions above)
s = Solution()
print(s.aggressiveCows([1, 2, 4, 8, 9], 3))     # 3
print(s.aggressiveCows([10, 1, 2, 7, 5], 3))    # 4  (cows at 1, 5, 10)
print(s.aggressiveCows([2, 12, 11, 3, 26, 7], 5))  # 1
print(s.aggressiveCows([0, 100000000], 2))      # 100000000

Based on this video: Aggressive Cows | Binary Search on Answer