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

Magnetic Force Between Two Balls

Balls are dropped into baskets along a line, and the force between two balls depends on how far apart they are. We want the weakest pull (the closest pair of balls) to be as far apart as possible. The teacher says straight away that this is the Aggressive Cows problem with new names: stalls become baskets and cows become balls. She builds the brute force (try every gap), runs it to show the TLE on LeetCode, and then switches the outer loop to binary search on the answer.

Why it matters: seeing the same idea under a different story is how you learn to recognise the pattern. Her closing advice: binary search on answer comes down to four jobs: find low, find high, write the check function, and decide which way to move.

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 needs something sorted, or more generally a yes/no question whose answer changes only once as you move left to right. Keep two ends, low and high, test the middle mid, and throw away the half that can't contain the answer. The range halves every step, so it takes about log₂(size) steps.

How to compute mid, and why that form

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

This equals (low + high) // 2, but in Java or C++ the sum low + high can be bigger than the largest int (2³¹ − 1 ≈ 2.1 × 10⁹) and turn into garbage. The teacher points to this problem's numbers: positions go up to 10⁹, so high can be close to 10⁹, and she prefers the safe form "whenever the constraints are big". Python integers never overflow, so in Python both forms give the right number. We keep the safe form as a habit.

The pattern: binary search on the answer

  1. Answer range: the smallest answer that could make sense (low) and the largest (high).
  2. Yes/no check: a function that tests one candidate answer: "with this value, is it possible?"
  3. Monotonic: along the range, all the yeses are on one side and all the noes on the other. So one test at mid tells us which half to drop.

The hint that you're in this pattern: the answer is a number like a distance, a speed or a sum that is not picked from the array, and the wording is "maximum of the minimum" or "minimum of the maximum".

Maximise the minimum vs minimise the maximum

maximise the minimum (this page, Aggressive Cows)minimise the maximum (Allocate Pages, Split Array)
candidate means"balls must be at least this far apart""no group may be heavier than this"
yes/no lineY Y Y N N N (small is easy)N N N Y Y Y (big is easy)
we wantthe last yesthe first yes
check(mid) yessave, low = mid + 1save, high = mid - 1
check(mid) nohigh = mid - 1low = mid + 1

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

LeetCode 1552

1The question in simple words

The array position lists where the baskets are on a line. All positions are different. We have m balls, at most one per basket. The magnetic force between two balls is just their distance. Place all m balls so that the smallest distance between any two of them is as large as possible, and return that distance.

Example 1: position = [1, 2, 3, 4, 7], m = 3 → answer 3.

index01234
position12347
line:  1  2  3  4  .  .  7
best:  B        B        B      gaps 3 and 3 → smallest = 3
weak:  B  B  B                  gaps 1 and 1 → smallest = 1

Balls at 1, 2, 3 all fit, but they're only 1 apart, which is a strong force. Balls at 1, 4, 7 are 3 apart both times. We can't do better, so the answer is 3. Like the teacher says, we must push the balls as far apart as we can, then report the minimum gap of that best placement.

Example 2: position = [5, 4, 3, 2, 1, 1000000000], m = 2 → answer 999999999 (one ball at 1, one at 10⁹). Notice the input isn't sorted.

2What the constraints tell us

3Intuition

Flip the question: "if every two balls must be at least g apart, can I still place all m balls?" That's a yes/no question we can answer with one walk along the line. Try g = 1, 2, 3, … and keep the largest g that says yes.

4Building the logic from examples

The teacher tries each gap on [1, 2, 3, 4, 7], m = 3

Doubt: in the video, the gap-2 placement is first said as 1, 3, 5, and for gap 4 the second ball is first put "at 5". Is there a basket at 5?
→ No. The baskets are only at 1, 2, 3, 4, 7. She was counting on the number line for a moment. Later in the video she gives the correct placements: 1, 3, 7 for gap 2, and 1, 7 for gap 4. The results (gap 2 yes, gap 4 no) don't change.

The yes/no line over all gaps

gap123456
all fit?YYYNNNanswer = the last Y = 3
Doubt: why does the line flip only once?
→ If gap g works, the same placement also has every pair at least g−1 apart, so all smaller gaps work. If g fails, a bigger gap asks for even more room, so it fails too. The teacher says this from the top end too: if 3 works, then 2 and 1 work as well.

low = 1

All balls can't share one basket (one ball per basket), so two balls are at least 1 apart. The smallest gap worth trying is 1.

high = max position − min position

The teacher first wonders whether high should be "infinity" or 10⁹. Then she uses the smallest m, which is 2: with only two balls, you put one at each end. That gives the biggest distance possible, 7 − 1 = 6 here. More balls can only bring the closest pair nearer. So high = max − min, and after sorting that's position[-1] - position[0].

Why sort the positions?

Example 2 is not sorted. After sorting, the check can walk left to right and only look at the next basket: if it's too close, move on, and the ones after it will be farther. Without sorting, a "too close" basket doesn't tell you anything about the rest, so for every ball you'd need another loop over all baskets to find a valid one. One sort up front is the cheapest option. Sorting also makes last − first equal to max − min.

The check: canPlace(position, m, gap)

Doubt 1: why "at least gap" and not exactly gap?
→ The next basket might not be at exactly last + gap. The teacher's example: if there were no basket at 2 and we want gap 1, then 3 (gap 2) is still fine. A bigger distance is never a problem, since we only need at least g. So the test is >=.
Doubt 2: why measure from last (the most recent ball)?
→ The most recent ball is the nearest one to the next basket. After placing at 1 and 2 (gap 1), basket 3 must be compared with 2, not with 1. The teacher stores the position value in last. She mentions you could store the index instead. Both work.
Doubt 3: why is it safe to always start at the first basket and place greedily?
→ Putting a ball as early as possible leaves the most room for the balls after it. So if this greedy walk can't fit m balls with gap g, no other placement can either.

Scanning the gaps: save on yes, stop on the first no

Going up from gap 1: gap 1 says yes, but it's not the largest, so don't return it. Save it in answer. Gap 2 → save 2. Gap 3 → save 3. Gap 4 → no. Now answer still holds 3, and every bigger gap will also say no, so break and return 3.

Or go down from high: 6 no, 5 no, 4 no, 3 yes → return 3 immediately. The first yes from the top is the largest, so no answer variable is needed.

5Approach steps

  1. Sort position.
  2. low = 1, high = position[-1] - position[0].
  3. For gap = low … high: if canPlace says yes, answer = gap; else break.
  4. Return answer.

6Code (Python)

Magnetic Force, brute force (gives TLE on LeetCode)
class Solution:
    def maxDistance(self, position, m):
        position.sort()
        low = 1
        high = position[-1] - position[0]
        answer = 0
        for gap in range(low, high + 1):
            if self.canPlace(position, m, gap):
                answer = gap         # possible: save and try bigger
            else:
                break                # first "no": bigger gaps fail too
        return answer

    def canPlace(self, position, m, gap):
        count = 1                    # first ball in the first basket
        last = position[0]
        for i in range(1, len(position)):
            if position[i] - last >= gap:
                count += 1
                last = position[i]
                if count >= m:       # already placed enough, stop early
                    return True
        return count >= m

7Code line by line

linewhat it means
position.sort()Baskets in line order, so "too close" means "move on", and last − first is the full length.
low = 1Two balls are always at least 1 apart.
high = position[-1] - position[0]Two balls at the two ends, which is the best any placement can do.
answer = gapThis gap works. Remember it and try the next bigger one.
breakThe first no. Every gap after it is also no.
count = 1 last = position[0]The first ball goes in the first basket.
if position[i] - last >= gap:Is this basket at least gap away from the latest ball?
count += 1 last = position[i]Place a ball, and measure future baskets from here.
if count >= m: return TrueThe teacher's early exit: once m balls are in, the answer is already yes.
return count >= mAfter the walk: did we place at least m balls?

8Dry run (hand table)

position = [1, 2, 3, 4, 7], m = 3. low = 1, high = 6.

gapballs atcountcheckanswer after
11, 2, 3 (stops early)3yes1
21, 3, 73yes2
31, 4, 73yes3
41, 72no → break3

Return 3 ✓.

What happens on Example 2: high = 10⁹ − 1, and every gap up to that says yes (two balls at the two ends). So the loop really does run about 10⁹ times. The teacher shows that the code runs fine on Example 1 if you cap high at 10³ (10³ × 10⁵ = 10⁸ steps), but on submit, Example 2 alone gives TLE.

9Complexity & remember

The walk over the baskets must stay, because each basket has to be looked at. But the gaps 1, 2, …, high are a sorted number line with one Y→N flip, so we can binary search over them.

Remember brute forceSort → try gaps 1 … (last − first) → greedy check with last → save on yes, break on the first no. Too slow when positions reach 10⁹.

Part B · Optimal: binary search on the gap

1The question

Same problem, same low, high and canPlace. We only change how we pick which gap to test: always the middle of what's left.

2Constraints

Same as Part A. log₂(10⁹) ≈ 30, so about 30 checks instead of 10⁹.

3Intuition

The line is Y Y Y N N N and we want the last Y. The teacher's reasoning: when gap 1 worked, we looked at 2. When 2 worked, we looked at 3. So a yes always sends us toward bigger gaps.

4Building the logic

Doubt 1: does low + high really overflow here in Java?
→ The teacher gives this problem as the reason for the safe form. Strictly, the biggest high is 10⁹ − 1 and low ≤ high, so low + high stays just under 2 × 10⁹, which is a bit below Java's limit of about 2.147 × 10⁹. It's close to the edge, though, and in other problems it does overflow. So the safe form is the right habit. In Python there's no overflow at all.
Doubt 2: why keep an answer variable?
→ When mid says yes, we move low past it. If no bigger gap works later, we'd have lost mid. Saving it first keeps the best yes seen so far. The teacher starts answer at 0. It always gets replaced, because gap 1 always works.

The answer range shrinking (Example 1)

gap123456
step 1123456mid 3 → yes → save 3, drop 1…3
step 2123456mid 5 → no → drop 5…6
step 3123456mid 4 → no → high = 3 < low = 4, stop

5Approach steps

  1. Sort. low = 1, high = position[-1] - position[0], answer = 0.
  2. While low <= high: mid = low + (high - low) // 2.
  3. canPlace(mid) yes → answer = mid, low = mid + 1. No → high = mid - 1.
  4. Return answer.

6Code (Python)

Magnetic Force, binary search on the answer
class Solution:
    def maxDistance(self, position, m):
        position.sort()
        low = 1
        high = position[-1] - position[0]
        answer = 0
        while low <= high:
            mid = low + (high - low) // 2      # candidate minimum gap
            if self.canPlace(position, m, mid):
                answer = mid                   # possible: save it
                low = mid + 1                  # look for a bigger gap
            else:
                high = mid - 1                 # too big: look smaller
        return answer

    def canPlace(self, position, m, gap):
        count = 1
        last = position[0]
        for i in range(1, len(position)):
            if position[i] - last >= gap:
                count += 1
                last = position[i]
                if count >= m:
                    return True
        return count >= m

7Code line by line

linewhat it means
while low <= high:Untested gaps remain. <= so the last single candidate also gets tested.
mid = low + (high - low) // 2The middle gap. Overflow-safe in any language.
answer = mid low = mid + 1mid works. Keep it, then look only at bigger gaps (we want the maximum).
high = mid - 1mid doesn't work, so nothing bigger does. Look only at smaller gaps.
return answerThe largest gap that said yes.
canPlace(...)Exactly the same as in Part A.

8Dry run (hand table)

position = [1, 2, 3, 4, 7], m = 3.

steplowhighmidballs atcheck(mid)decisionthrown away
11631, 4, 7yesanswer = 3, low = 4gaps 1…3
24651, 7nohigh = 4gaps 5…6
34441, 7nohigh = 3gap 4
end43low > high → return 3 ✓

Example 2 (sorted: 1, 2, 3, 4, 5, 10⁹; m = 2): every mid says yes (the two ends are always far enough apart), so low keeps moving right, and after about 30 steps answer = high = 999999999 ✓.

9Complexity & remember

Remember the optimal wayIt's Aggressive Cows: sort, low = 1, high = last − first, greedy check. Binary search on the gap, yes → save and go right, no → go left.

Part C · Revision page

Aggressive CowsMagnetic Force
things on a linestallsbaskets
what we placek cowsm balls
maximisethe smallest distance between any two placed items
range / check / movesidentical: low = 1, high = last − first, greedy walk with last, yes → right
constraintsn ≤ 10⁶, pos ≤ 10⁸n ≤ 10⁵, pos ≤ 10⁹
A · brute forceB · binary search
gaps tried1, 2, 3, … until the first noabout log₂(10⁹) ≈ 30 mids
time≈ 10⁹ × 10⁵ = 10¹⁴ (TLE)≈ 30 × 10⁵ = 3 × 10⁶
maximise the minimumminimise the maximum
lineY Y Y N NN N Y Y Y
on yeslow = mid + 1high = mid - 1
If you remember only 5 lines 1. Force = distance. Maximise the closest pair's distance → "max of min".
2. Check: can m balls fit if each pair must be ≥ g apart? Greedy from the first basket.
3. low = 1, high = last − first (2 balls at the ends). Sort first.
4. Small g → yes, big g → no, one flip → binary search.
5. Yes → answer = mid, low = mid + 1. No → high = mid − 1.
Mistakes to avoid ✗ forgetting to sort (Example 2 isn't sorted)
✗ not updating last after placing a ball
✗ == or > instead of >= for the distance
✗ returning on the first yes when scanning upward (gives 1)
✗ going left on yes (that's the min-of-max direction)
test it yourself (paste under any of the solutions above)
s = Solution()
print(s.maxDistance([1, 2, 3, 4, 7], 3))                 # 3
print(s.maxDistance([5, 4, 3, 2, 1, 1000000000], 2))     # 999999999
print(s.maxDistance([1, 2], 2))                          # 1
print(s.maxDistance([79, 74, 57, 22], 4))                # 5

Run the second line with the binary search version only. The brute force would try about 10⁹ gaps there, which is exactly the TLE from the video.

Based on this video: Magnetic Force Between Two Balls | Binary Search on Answer