Aggressive Cows — Binary Search on Answer (SPOJ / GFG Classic)
Advertisement
Problem Statement
You are given
Nstall positions along a number line andCcows. Place the cows in distinct stalls such that the minimum distance between any two cows is maximised. Return that maximum possible minimum distance.
This is the classic SPOJ AGGRCOW / GeeksforGeeks problem. The same logic appears as LC 2064 (Magnetic Force Between Two Balls) and LC 1552 (Magnetic Force Between Two Balls — exact same problem, renamed).
Constraints:
2 <= N <= 10^52 <= C <= N0 <= stall[i] <= 10^9- All stall positions are distinct
Example 1:
Input: stalls = [1, 2, 4, 8, 9], C = 3
Output: 3
Explanation: Place cows at positions 1, 4, 9.
Min distances: 4-1=3, 9-4=5 → minimum is 3.
No placement achieves a minimum gap > 3.Example 2:
Input: stalls = [0, 3, 4, 7, 10, 9], C = 4
Output: 3
Explanation: Sort to [0,3,4,7,9,10].
Place at 0, 3, 7, 10 → gaps 3, 4, 3 → min = 3.Example 3:
Input: stalls = [1, 2], C = 2
Output: 1
Explanation: Only two stalls, two cows — gap is always 1.Why This Problem Matters
Aggressive Cows is the canonical teaching example for binary search on the answer space, also called "parametric binary search." It appears by name in competitive programming curricula worldwide (SPOJ AGGRCOW has been solved over 50 000 times) and its logic is embedded in dozens of LeetCode hard problems.
The key shift in thinking: instead of binary searching within the input for a target value, you binary search over all possible answers and check whether a given answer is achievable. This pattern unlocks an entire category of optimisation problems that appear intractable until you realise the feasibility function is monotone.
In FAANG interviews, this pattern typically surfaces in the form of "minimise the maximum" or "maximise the minimum" problems. Interviewers expect you to recognise the monotonicity property, define the feasibility check, and write the correct upper-bound binary search template. Aggressive Cows is the fastest way to internalise all three.
The Core Insight
Observation: If you can place all C cows with a minimum gap of d, then you can also place them with any gap smaller than d. Conversely, if a gap of d is impossible, then any gap larger than d is also impossible.
This monotone feasibility means: given a candidate minimum distance mid, just greedily place cows one by one, always choosing the next stall at least mid away from the last placed cow. If you can place all C cows, then mid is feasible; otherwise it is not.
Binary search on mid in the range [1, stalls[-1] - stalls[0]] and find the largest feasible value.
Template — maximise the minimum (upper-bound variant):
lo = 1, hi = max_possible
while lo < hi:
mid = lo + (hi - lo + 1) // 2 # bias toward right to avoid infinite loop
if feasible(mid): lo = mid # mid works, try larger
else: hi = mid - 1 # mid too large
return loThe +1 bias in mid is critical: without it, when lo = hi - 1 and feasible(lo) is true, mid stays at lo forever.
Visual Dry Run
Input: stalls = [1, 2, 4, 8, 9], C = 3
After sorting: [1, 2, 4, 8, 9]
Search range: lo = 1, hi = 9 - 1 = 8
| Step | lo | hi | mid | Greedy placement | Cows placed | Feasible? |
|---|---|---|---|---|---|---|
| 1 | 1 | 8 | 5 | 1 → 8 (gap 7 >= 5) — done at 2 cows | 2 | No (need 3) |
| 2 | 1 | 4 | 3 | 1 → 4 (gap 3 >= 3) → 8 (gap 4 >= 3) — 3 cows | 3 | Yes → lo=3 |
| 3 | 3 | 4 | 4 | 1 → (no stall with gap >= 4 after 1 except 8) → 1, 8 — 2 cows | 2 | No → hi=3 |
| — | 3 | 3 | — | loop exits | — | return 3 |
Common Mistakes
-
Using
mid = (lo + hi) // 2in the upper-bound template. Whenlo = hi - 1andfeasible(lo)is true,midcomputes tolo, the condition setslo = mid = lo, and the loop never terminates. Always uselo + (hi - lo + 1) // 2for the "maximise" variant. -
Forgetting to sort the stalls. The greedy placement only works on a sorted array. Skipping the sort produces wrong answers silently.
-
Setting
hi = len(stalls) - 1instead ofstalls[-1] - stalls[0]. The binary search is over distances, not indices. Using an index as the upper bound is a conceptual error. -
Off-by-one in the feasibility check. The first cow is always placed at
stalls[0]. Starting the count at0and then incrementing gives the wrong final count. -
Using a minimise template for a maximise problem. The "minimise" template biases
midtoward the left; the "maximise" template biases it toward the right. Mixing them produces incorrect boundary behavior. -
Not handling the edge case where
C == 1. With only one cow, any placement works and the answer is the full span. The algorithm handles this correctly if coded properly, but verify it.
Solutions
Python
def aggressiveCows(stalls: list[int], c: int) -> int:
stalls.sort() # sort stalls — mandatory for greedy check
def feasible(min_gap: int) -> bool:
"""Return True if we can place c cows with every gap >= min_gap."""
count = 1 # place first cow at the leftmost stall
prev = stalls[0] # track position of last placed cow
for stall in stalls[1:]: # try each remaining stall in order
if stall - prev >= min_gap: # this stall is far enough from the last cow
count += 1 # place a cow here
prev = stall # update last placed position
if count == c: # early exit — all cows placed
return True
return count >= c # did we place all c cows?
lo = 1 # minimum possible gap is 1
hi = stalls[-1] - stalls[0] # maximum possible gap is full span
while lo < hi:
mid = lo + (hi - lo + 1) // 2 # bias right: maximise-the-minimum template
if feasible(mid):
lo = mid # mid is achievable — try a larger gap
else:
hi = mid - 1 # mid is too large — reduce upper bound
return lo # largest feasible minimum gapJavaScript
function aggressiveCows(stalls, c) {
stalls.sort((a, b) => a - b); // sort stalls numerically
function feasible(minGap) {
// Check if we can place c cows with every consecutive gap >= minGap
let count = 1; // first cow at leftmost stall
let prev = stalls[0]; // position of last placed cow
for (let i = 1; i < stalls.length; i++) {
if (stalls[i] - prev >= minGap) { // stall is far enough
count++; // place cow
prev = stalls[i]; // update last position
if (count === c) return true; // all cows placed — early exit
}
}
return count >= c; // check if all c cows were placed
}
let lo = 1; // smallest possible gap
let hi = stalls[stalls.length - 1] - stalls[0]; // largest possible gap
while (lo < hi) {
const mid = lo + Math.floor((hi - lo + 1) / 2); // bias right — maximise variant
if (feasible(mid)) {
lo = mid; // achievable — try larger
} else {
hi = mid - 1; // too large — shrink upper bound
}
}
return lo; // maximum achievable minimum gap
}Complexity Analysis
| Step | Time | Space | Notes |
|---|---|---|---|
| Sorting | O(n log n) | O(1) | One-time pre-processing |
| Feasibility check | O(n) | O(1) | Single greedy pass |
| Binary search iterations | O(log(max - min)) | O(1) | Range over distances |
| Total | O(n log(max - min)) | O(1) | max - min <= 10^9 → ~30 iterations |
Follow-up Questions
- What changes if you want to minimise the maximum gap? Swap to the "minimise" template:
mid = lo + (hi - lo) // 2,if feasible(mid): hi = midelselo = mid + 1. This is the pattern for LC 1760 (Minimum Limit of Balls in a Bag). - LC 2064 Magnetic Force Between Two Balls is identical to this problem — same constraints, same code.
- What if stalls have capacity limits (place multiple cows per stall)? The feasibility function changes; the binary search shell stays the same.
- Can the binary search be over a continuous domain? Yes — replace integer binary search with a floating-point epsilon loop. Used in LC 786 (Kth Smallest Prime Fraction).
This Pattern Solves
- SPOJ AGGRCOW / GFG Aggressive Cows (this problem)
- LC 2064 — Minimum Number of Days to Make m Bouquets
- LC 1552 / LC 2064 — Magnetic Force Between Two Balls
- LC 1011 — Capacity to Ship Packages Within D Days
- LC 410 — Split Array Largest Sum
- LC 875 — Koko Eating Bananas
- LC 1870 — Minimum Speed to Arrive on Time
- Any "maximise the minimum" or "minimise the maximum" optimisation problem with a greedy feasibility check
Key Takeaway
Whenever you see "maximise the minimum" or "minimise the maximum," think binary search on the answer. Define a greedy feasibility function, verify it is monotone (once feasible at d, feasible at all d' < d), then apply the correct upper-bound template with the right-biased midpoint. The stall sorting + greedy placement pair is the canonical two-step for Aggressive Cows and its many disguised variants.
Key Takeaways
- Aggressive Cows is the canonical "maximise the minimum" binary search on answer problem; its exact logic appears in LC 1552, LC 2064, and many competitive programming problems.
- Feasibility is monotone: if placing cows with a minimum gap of
dis possible, then any gapd' < dis also possible — this monotonicity is the prerequisite for binary search. - The greedy feasibility check: sort stalls, place the first cow at position 0, then each subsequent cow at the earliest stall at least
gapaway — O(n) per check. - For maximisation, use the upper-mid formula
lo + (hi - lo + 1) // 2and setlo = midon success — this prevents infinite loops whenlo + 1 == hi. - Search bounds:
lo = 1(smallest gap),hi = (stalls[-1] - stalls[0]) / (C - 1)(tightest upper bound assuming even spread). - Sorting the stalls before any binary search or greedy reasoning is mandatory — without sorted positions, the gap calculations are meaningless.
- This template is reused verbatim for LC 1552 (Magnetic Force Between Two Balls), LC 2064 (Distributed Candies), and any "maximise minimum separation" problem.
Advertisement