Searching & Sorting · 24 of 36
Aggressive cows
Put grumpy cows into stalls along a line so that the two cows closest together are still as far apart as possible.
- Simple way
- time O(n log n + n * (max - min))
room O(1) - Clever way
- time O(n log n + n log(max - min))
room O(1)
The problem, simply
A barn has n stalls in one long row. You get the position of each stall, like house numbers on a street. You also get c cows. The cows are grumpy and kick each other when they stand close.
Put every cow in its own stall. Look at the two cows that ended up closest together. Make that smallest gap as big as you can, and give back that gap.
A story to remember it
Picture it
Stalls at 1, 2, 4, 8 and 9 with 3 cows. Cows at 1, 4 and 8 leave gaps of 3 and 4. The smallest gap is 3, and no layout does better.
- checking
- answer
Examples
A normal case
- In
stalls = [1, 2, 4, 8, 9], cows = 3- Out
3
Cows at 1, 4 and 8. Gaps are 3 and 4, so the smallest is 3. Asking for 4 fails: after 1 the next far enough stall is 8, and then nothing is 4 past 8.
Stalls not in order
- In
stalls = [10, 1, 2, 7, 5], cows = 3- Out
4
Sorted they are 1, 2, 5, 7, 10. Cows at 1, 5 and 10 give gaps 4 and 5. The input order means nothing, so sort first.
Only two cows
- In
stalls = [1, 50, 100], cows = 2- Out
99
With two cows, put one at each end of the barn. The gap is 100 - 1 = 99.
As many cows as stalls
- In
stalls = [1, 3, 6, 10], cows = 4- Out
2
Every stall gets a cow, so there is no choice. The closest neighbours are 1 and 3.
Way 1: the simple way
Try every gap, starting at 1 and going up. For each gap, check if all cows fit. The first gap that fails means every bigger gap fails too, so the gap just before it is the answer.
To check one gap, use the sleeping bag trick:
- Sort the stalls.
- Put the first cow in the first stall.
- Walk right. Put the next cow in the first stall that is at least
gapaway from the last cow. - If you placed all
ccows, the gap works.
Putting each cow as far left as allowed is never worse, because it leaves the most room for the cows after it.
import java.util.*;
class Solution {
// Can all cows stand at least gap apart?
boolean canPlace(int[] stalls, int cows, int gap) {
int placed = 1, last = stalls[0]; // first cow in the first stall
for (int i = 1; i < stalls.length; i++) {
if (stalls[i] - last >= gap) { // far enough from the last cow
placed++;
last = stalls[i];
if (placed == cows) return true;
}
}
return placed >= cows;
}
public int aggressiveCows(int[] stalls, int cows) {
Arrays.sort(stalls);
int most = stalls[stalls.length - 1] - stalls[0];
int best = 0;
for (int gap = 1; gap <= most; gap++) {
if (canPlace(stalls, cows, gap)) best = gap;
else break; // bigger gaps fail too
}
return best;
}
}How long it takes: sorting is O(n log n). Then each gap costs one walk of n stalls, and there can be max - min gaps to try. Stalls can be a billion apart, so O(n * (max - min)) is far too slow. Extra room: O(1).
Way 2: the clever way
The answers line up like a row of switches: small gaps all say yes, big gaps all say no. Somewhere there is a last yes. Finding the edge between yes and no in a sorted row is the job of binary search, so do binary search on the answer instead of on the stalls.
- Sort the stalls. Let
lo = 1andhi = max - min. - Take the middle gap
mid. Run the sleeping bag check. - If it works, remember
midand look bigger:lo = mid + 1. - If it fails, look smaller:
hi = mid - 1. - When
lopasseshi, the remembered gap is the answer.
The bottom row in the animation is the list of gaps we could answer. Gaps marked rejected are too big. Gaps marked done are known to work.
Guess a gap, check it, throw away half the guesses
01 / 08
Sort the stalls. The smallest gap we could ask for is 1. The biggest is 9 - 1 = 8, the whole barn. The answer is somewhere in 1 to 8.
- cows
- 3
- checking
- done
- answer
- rejected
import java.util.*;
class Solution {
boolean canPlace(int[] stalls, int cows, int gap) {
int placed = 1, last = stalls[0];
for (int i = 1; i < stalls.length; i++) {
if (stalls[i] - last >= gap) {
placed++;
last = stalls[i];
if (placed == cows) return true;
}
}
return placed >= cows;
}
public int aggressiveCows(int[] stalls, int cows) {
Arrays.sort(stalls);
int lo = 1, hi = stalls[stalls.length - 1] - stalls[0];
int best = 0;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2; // no overflow
if (canPlace(stalls, cows, mid)) {
best = mid; // it works, try a bigger gap
lo = mid + 1;
} else {
hi = mid - 1; // too big, try a smaller gap
}
}
return best;
}
}How long it takes: sorting is O(n log n). The guessing game halves the range each time, so it makes about log(max - min) guesses (about 30 for a billion), and each guess walks the stalls once: O(n log(max - min)). Extra room: O(1) besides the sort.
Watch out for
- Moving the wrong pointer. Here a working gap means "try bigger", so
lomoves. In "minimize the biggest" problems like book allocation it is the other way round. - Writing
(lo + hi) / 2with positions near two billion can overflow. Uselo + (hi - lo) / 2. - Starting
loat 0 is harmless but pointless: a gap of 0 always works. - Returning
loinstead of the remembered answer. With this loop, the answer isbest(which equalshiat the end).
What interviewers ask next
- Why is the greedy check correct? Any working layout can slide its first cow to the first stall, then its second cow left to the first far enough stall, and so on, without breaking any gap. So if any layout works, the greedy one works.
- Can you shrink the search range? Yes. With
ccows there arec - 1gaps that add up to at mostmax - min, sohi = (max - min) / (c - 1). - Same idea, different story: LeetCode "Magnetic Force Between Two Balls" is this exact problem.
- What if positions are real numbers? Binary search on a double for a fixed number of rounds (say 100), or until
hi - lois tiny. - How do you spot this pattern? The words "maximize the minimum" or "minimize the maximum", plus a yes or no check that flips only once as the answer grows.
Remember this
Grown-up words: the guessing game is binary search on the answer, the yes then no row is a monotonic predicate, the sleeping bag trick is a greedy feasibility check, and the smallest gap we push up is the max-min distance.