Magnetic Force Between Two Balls
Binary search the largest minimum gap such that m balls can be greedily placed in the sorted positions.
4 min read
binary-search greedy parametric-search
Problem#
Given basket positions and a count m, place m balls so the minimum pairwise distance is maximized. Return that minimum.
Examples#
position = [1,2,3,4,7], m = 3→3(place at 1, 4, 7)position = [5,4,3,2,1,1000000000], m = 2→999999999
Constraints#
n == position.length,2 <= n <= 10^5,1 <= position[i] <= 10^9,2 <= m <= n.
Approach#
The number of balls that can be placed with min gap at least d is non-increasing in d. Binary search d in [1, max - min]; for each d, greedily place balls left-to-right whenever the next basket is at least d past the last placement.
Solution#
class Solution {public: int maxDistance(vector<int>& position, int m) { sort(position.begin(), position.end()); auto canPlace = [&](int d) { int count = 1, last = position[0]; for (int i = 1; i < (int)position.size(); ++i) { if (position[i] - last >= d) { ++count; last = position[i]; if (count >= m) return true; } } return false; }; int lo = 1, hi = position.back() - position.front(); while (lo < hi) { int mid = lo + (hi - lo + 1) / 2; if (canPlace(mid)) lo = mid; else hi = mid - 1; } return lo; }};import "sort"
func maxDistance(position []int, m int) int { sort.Ints(position) canPlace := func(d int) bool { count, last := 1, position[0] for i := 1; i < len(position); i++ { if position[i]-last >= d { count++ last = position[i] if count >= m { return true } } } return false } lo, hi := 1, position[len(position)-1]-position[0] for lo < hi { mid := lo + (hi-lo+1)/2 if canPlace(mid) { lo = mid } else { hi = mid - 1 } } return lo}from typing import List
class Solution: def maxDistance(self, position: List[int], m: int) -> int: position.sort()
def can_place(d: int) -> bool: count, last = 1, position[0] for i in range(1, len(position)): if position[i] - last >= d: count += 1 last = position[i] if count >= m: return True return False
lo, hi = 1, position[-1] - position[0] while lo < hi: mid = lo + (hi - lo + 1) // 2 if can_place(mid): lo = mid else: hi = mid - 1 return lofunction maxDistance(position, m) { position.sort((a, b) => a - b); const canPlace = (d) => { let count = 1, last = position[0]; for (let i = 1; i < position.length; i++) { if (position[i] - last >= d) { count++; last = position[i]; if (count >= m) return true; } } return false; }; let lo = 1, hi = position[position.length - 1] - position[0]; while (lo < hi) { const mid = lo + Math.floor((hi - lo + 1) / 2); if (canPlace(mid)) lo = mid; else hi = mid - 1; } return lo;}class Solution { public int maxDistance(int[] position, int m) { Arrays.sort(position); int lo = 1, hi = position[position.length - 1] - position[0]; while (lo < hi) { int mid = lo + (hi - lo + 1) / 2; if (canPlace(position, m, mid)) lo = mid; else hi = mid - 1; } return lo; }
private boolean canPlace(int[] position, int m, int d) { int count = 1, last = position[0]; for (int i = 1; i < position.length; i++) { if (position[i] - last >= d) { count++; last = position[i]; if (count >= m) return true; } } return false; }}function maxDistance(position: number[], m: number): number { position.sort((a, b) => a - b); const canPlace = (d: number): boolean => { let count = 1, last = position[0]; for (let i = 1; i < position.length; i++) { if (position[i] - last >= d) { count++; last = position[i]; if (count >= m) return true; } } return false; }; let lo = 1, hi = position[position.length - 1] - position[0]; while (lo < hi) { const mid = lo + Math.floor((hi - lo + 1) / 2); if (canPlace(mid)) lo = mid; else hi = mid - 1; } return lo;}Editorial#
The greedy placement is optimal for the feasibility check: pushing each ball as early as possible only helps later balls. The upper-mid binary search (lo + (hi - lo + 1) / 2) avoids infinite loops when the predicate is “feasible → keep”.
Complexity#
- Time: O(n log n + n log M).
- Space: O(1) extra.
Concept revision#
Related#