1552. Magnetic Force Between Two Balls
Problem Description
You have n
baskets at different positions along a line, where the i-th
basket is located at position[i]
. You need to place m
balls into these baskets.
When two balls are placed in baskets, they exert a magnetic force on each other. The magnetic force between two balls at positions x
and y
is defined as the absolute distance between them: |x - y|
.
Your goal is to distribute the m
balls into the baskets in such a way that the minimum magnetic force between any two balls is maximized. In other words, you want to place the balls so that the closest pair of balls is as far apart as possible.
Given:
- An integer array
position
representing the positions of then
baskets - An integer
m
representing the number of balls to place
Return the maximum possible value of the minimum magnetic force between any two balls.
For example, if you have baskets at positions [1, 2, 3, 4, 7]
and need to place 3 balls, one possible arrangement could be placing balls at positions 1, 4, and 7. This would give magnetic forces of |1-4| = 3
, |1-7| = 6
, and |4-7| = 3
. The minimum magnetic force in this arrangement is 3.
Intuition
The key insight is recognizing that this problem has a monotonic property: as we increase the minimum required distance between balls, the number of balls we can successfully place decreases. If we can place m
balls with a minimum distance of d
, then we can definitely place m
balls with any minimum distance less than d
(we just have more flexibility in placement).
This monotonic relationship suggests using binary search on the answer. Instead of directly trying to find the optimal placement, we can flip the problem: given a specific minimum distance f
, can we place all m
balls such that every pair is at least f
apart?
To check if a given minimum distance f
is achievable, we can use a greedy approach: sort the basket positions and place balls from left to right, always placing the next ball in the leftmost available basket that is at least distance f
from the previous ball. This greedy strategy works because placing a ball as far left as possible gives us the most room for placing subsequent balls.
The search space for our answer is bounded. The minimum possible distance is 1 (when balls are in adjacent positions), and the maximum possible distance cannot exceed the total span of the baskets (position[-1] - position[0]
after sorting). We binary search within this range to find the largest minimum distance that still allows us to place all m
balls.
The algorithm transforms the optimization problem ("maximize the minimum distance") into a decision problem ("can we achieve at least this minimum distance?"), which is easier to solve and perfect for binary search.
Learn more about Binary Search and Sorting patterns.
Solution Approach
The solution implements binary search combined with a greedy verification function. Here's how it works:
1. Sort the positions array: First, we sort the basket positions to enable the greedy placement strategy. This allows us to traverse from left to right when placing balls.
2. Set up binary search bounds: We initialize the search range with l = 1
(minimum possible distance) and r = position[-1]
(the rightmost position, which represents an upper bound for the maximum distance).
3. Define the verification function check(f)
: This function determines if we can place m
balls with a minimum distance of at least f
between any two balls. The implementation uses a greedy approach:
- Start with
prev = -inf
to ensure the first ball can always be placed - Initialize a counter
cnt = 0
to track the number of balls placed - Iterate through each basket position
curr
- If
curr - prev >= f
, we can place a ball here:- Update
prev = curr
(mark this as the last placed position) - Increment
cnt += 1
- Update
- Return
cnt < m
(returnsTrue
if we couldn't place allm
balls)
4. Apply binary search: The solution uses bisect_left
to find the boundary where the check
function transitions from False
to True
. Since check(f)
returns True
when we cannot place all m
balls with distance f
, we're looking for the largest value where check
returns False
(meaning we can place all balls).
The bisect_left(range(l, r + 1), True, key=check)
call finds the leftmost position where check
returns True
, which means the answer is one less than this value - the largest distance where we can still place all m
balls.
Time Complexity: O(n log n + n log(max_position))
where n
is the number of baskets. The sorting takes O(n log n)
, and the binary search performs O(log(max_position))
iterations, each requiring O(n)
time to verify.
Space Complexity: O(1)
excluding the sorting space, as we only use a few variables for the binary search and verification.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with baskets at positions [1, 2, 3, 4, 7]
and we need to place m = 3
balls.
Step 1: Sort positions
The array is already sorted: [1, 2, 3, 4, 7]
Step 2: Set binary search bounds
l = 1
(minimum possible distance)r = 7
(rightmost position)
Step 3: Binary search iterations
We'll binary search for the maximum minimum distance. Let's trace through the key iterations:
Iteration 1: Check mid = 4
- Can we place 3 balls with minimum distance 4?
- Start with
prev = -inf
,cnt = 0
- Position 1:
1 - (-inf) >= 4
✓, place ball,prev = 1
,cnt = 1
- Position 2:
2 - 1 = 1 < 4
✗, skip - Position 3:
3 - 1 = 2 < 4
✗, skip - Position 4:
4 - 1 = 3 < 4
✗, skip - Position 7:
7 - 1 = 6 >= 4
✓, place ball,prev = 7
,cnt = 2
- Result: Only placed 2 balls, need 3. Distance 4 is too large.
Iteration 2: Check mid = 2
- Start with
prev = -inf
,cnt = 0
- Position 1:
1 - (-inf) >= 2
✓, place ball,prev = 1
,cnt = 1
- Position 2:
2 - 1 = 1 < 2
✗, skip - Position 3:
3 - 1 = 2 >= 2
✓, place ball,prev = 3
,cnt = 2
- Position 4:
4 - 3 = 1 < 2
✗, skip - Position 7:
7 - 3 = 4 >= 2
✓, place ball,prev = 7
,cnt = 3
- Result: Successfully placed all 3 balls! Distance 2 is achievable.
Iteration 3: Check mid = 3
- Start with
prev = -inf
,cnt = 0
- Position 1:
1 - (-inf) >= 3
✓, place ball,prev = 1
,cnt = 1
- Position 2:
2 - 1 = 1 < 3
✗, skip - Position 3:
3 - 1 = 2 < 3
✗, skip - Position 4:
4 - 1 = 3 >= 3
✓, place ball,prev = 4
,cnt = 2
- Position 7:
7 - 4 = 3 >= 3
✓, place ball,prev = 7
,cnt = 3
- Result: Successfully placed all 3 balls! Distance 3 is achievable.
Step 4: Binary search converges The binary search continues narrowing the range. Since both distance 2 and 3 work, but distance 4 doesn't, the maximum achievable minimum distance is 3.
Final Answer: 3
The optimal placement is at positions 1, 4, and 7, giving distances:
- Between balls at 1 and 4:
|4 - 1| = 3
- Between balls at 4 and 7:
|7 - 4| = 3
- Between balls at 1 and 7:
|7 - 1| = 6
The minimum distance is 3, which is the maximum possible for this configuration.
Solution Implementation
1from typing import List
2from math import inf
3from bisect import bisect_left
4
5class Solution:
6 def maxDistance(self, position: List[int], m: int) -> int:
7 """
8 Find the maximum minimum distance between m balls placed in given positions.
9
10 Args:
11 position: List of available positions for placing balls
12 m: Number of balls to place
13
14 Returns:
15 Maximum possible minimum distance between any two balls
16 """
17
18 def can_place_balls(min_distance: int) -> bool:
19 """
20 Check if we can place m balls with at least min_distance apart.
21
22 Args:
23 min_distance: Minimum required distance between consecutive balls
24
25 Returns:
26 True if we CANNOT place m balls with given min_distance
27 (returns True when placement fails for bisect_left to work correctly)
28 """
29 previous_position = -inf # Initialize to negative infinity to place first ball
30 balls_placed = 0
31
32 # Greedily place balls from left to right
33 for current_position in position:
34 if current_position - previous_position >= min_distance:
35 # Can place a ball at current position
36 previous_position = current_position
37 balls_placed += 1
38
39 # Return True if we cannot place all m balls (for bisect_left logic)
40 return balls_placed < m
41
42 # Sort positions to enable greedy placement
43 position.sort()
44
45 # Binary search bounds: minimum distance is 1, maximum is the span of positions
46 left_bound = 1
47 right_bound = position[-1] - position[0]
48
49 # Find the first distance where we CANNOT place m balls
50 # Then subtract 1 to get the maximum valid distance
51 result = bisect_left(range(left_bound, right_bound + 1), True, key=can_place_balls)
52
53 # Adjust result since we're looking for the last valid distance
54 return left_bound + result - 1 if result > 0 else left_bound
55
1class Solution {
2 private int[] positions;
3
4 /**
5 * Find the maximum minimum distance between any two balls in m baskets
6 * @param position Array of basket positions
7 * @param m Number of balls to place
8 * @return Maximum minimum distance between any two balls
9 */
10 public int maxDistance(int[] position, int m) {
11 // Sort positions to enable binary search on distance
12 Arrays.sort(position);
13 this.positions = position;
14
15 // Binary search on the minimum distance
16 // left: minimum possible distance (1)
17 // right: maximum possible distance (last position - first position)
18 int left = 1;
19 int right = position[position.length - 1] - position[0];
20
21 // Binary search to find the maximum valid minimum distance
22 while (left < right) {
23 // Calculate mid point (using +1 to avoid infinite loop when left = right - 1)
24 int mid = (left + right + 1) >> 1;
25
26 // Check if we can place at least m balls with minimum distance of mid
27 if (countBallsWithMinDistance(mid) >= m) {
28 // If yes, try to find a larger minimum distance
29 left = mid;
30 } else {
31 // If no, reduce the minimum distance requirement
32 right = mid - 1;
33 }
34 }
35
36 return left;
37 }
38
39 /**
40 * Count maximum number of balls that can be placed with given minimum distance
41 * @param minDistance Minimum required distance between consecutive balls
42 * @return Maximum number of balls that can be placed
43 */
44 private int countBallsWithMinDistance(int minDistance) {
45 // Place first ball at the first position
46 int previousPosition = positions[0];
47 int ballCount = 1;
48
49 // Greedily place balls at positions that satisfy minimum distance requirement
50 for (int currentPosition : positions) {
51 if (currentPosition - previousPosition >= minDistance) {
52 // Place a ball at current position
53 ballCount++;
54 previousPosition = currentPosition;
55 }
56 }
57
58 return ballCount;
59 }
60}
61
1class Solution {
2public:
3 int maxDistance(vector<int>& position, int m) {
4 // Sort positions in ascending order
5 sort(position.begin(), position.end());
6
7 // Binary search range: minimum distance is 1, maximum is the range span
8 int left = 1;
9 int right = position.back() - position.front();
10
11 // Lambda function to count how many balls can be placed with minimum distance 'minDist'
12 auto countBallsPlaced = [&](int minDist) -> int {
13 int previousPosition = position[0]; // Place first ball at the first position
14 int ballCount = 1; // Count of balls placed
15
16 // Try to place balls at subsequent positions
17 for (int i = 1; i < position.size(); i++) {
18 int currentPosition = position[i];
19 // If current position is at least minDist away from previous ball
20 if (currentPosition - previousPosition >= minDist) {
21 previousPosition = currentPosition; // Place ball here
22 ballCount++;
23 }
24 }
25 return ballCount;
26 };
27
28 // Binary search for the maximum minimum distance
29 while (left < right) {
30 // Use upper mid to avoid infinite loop when left = right - 1
31 int mid = left + (right - left + 1) / 2;
32
33 // If we can place at least m balls with distance mid
34 if (countBallsPlaced(mid) >= m) {
35 left = mid; // Try for a larger distance
36 } else {
37 right = mid - 1; // Distance too large, reduce it
38 }
39 }
40
41 return left; // Maximum minimum distance found
42 }
43};
44
1/**
2 * Finds the maximum minimum distance between any two balls placed in positions
3 * @param position - Array of positions where balls can be placed
4 * @param m - Number of balls to place
5 * @returns Maximum possible minimum distance between any two balls
6 */
7function maxDistance(position: number[], m: number): number {
8 // Sort positions in ascending order to enable binary search
9 position.sort((a, b) => a - b);
10
11 // Binary search bounds: minimum distance is 1, maximum is the distance between first and last position
12 let left: number = 1;
13 let right: number = position[position.length - 1];
14
15 /**
16 * Counts how many balls can be placed with at least minDistance apart
17 * @param minDistance - Minimum required distance between consecutive balls
18 * @returns Number of balls that can be placed
19 */
20 const countBallsPlaced = (minDistance: number): number => {
21 let ballCount: number = 1; // First ball is always placed at position[0]
22 let previousPosition: number = position[0];
23
24 // Try to place balls greedily from left to right
25 for (const currentPosition of position) {
26 // If current position is at least minDistance away from previous ball
27 if (currentPosition - previousPosition >= minDistance) {
28 ballCount++;
29 previousPosition = currentPosition;
30 }
31 }
32
33 return ballCount;
34 };
35
36 // Binary search for the maximum minimum distance
37 while (left < right) {
38 // Calculate mid point, ceiling division to avoid infinite loop
39 const mid: number = (left + right + 1) >> 1;
40
41 // Check if we can place at least m balls with minimum distance of mid
42 if (countBallsPlaced(mid) >= m) {
43 // If yes, try to find a larger minimum distance
44 left = mid;
45 } else {
46 // If no, reduce the minimum distance requirement
47 right = mid - 1;
48 }
49 }
50
51 return left;
52}
53
Time and Space Complexity
Time Complexity: O(n × log n + n × log M)
The time complexity consists of two main parts:
O(n × log n)
for sorting theposition
array, wheren
is the length of the position arrayO(n × log M)
for the binary search operation, where:- The binary search runs
O(log M)
iterations, withM = position[-1] - 1
being the search range (from 1 to the maximum position value) - Each iteration calls the
check
function, which iterates through alln
positions inO(n)
time - Combined:
O(n × log M)
- The binary search runs
Space Complexity: O(log n)
The space complexity comes from:
- The sorting algorithm (typically Timsort in Python) uses
O(log n)
space for its recursion stack - The
check
function usesO(1)
additional space with only a few variables (prev
,cnt
,curr
) - The binary search itself uses
O(1)
space for variablesl
andr
- The dominant factor is the sorting's recursion stack:
O(log n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Binary Search Bounds
A common mistake is setting the right bound as position[-1]
(the maximum position value) instead of position[-1] - position[0]
(the maximum possible distance between two balls). This leads to unnecessary iterations and potential incorrect results.
Wrong:
right_bound = position[-1] # This is a position, not a distance
Correct:
right_bound = position[-1] - position[0] # Maximum possible distance
2. Off-by-One Error in Binary Search Result
The trickiest part is understanding what bisect_left
returns and how to interpret it. Since the check
function returns True
when we cannot place all balls, bisect_left
finds the first distance where placement fails. The maximum valid distance is one less than this value.
Wrong interpretation:
# Directly returning the bisect_left result
return bisect_left(range(left_bound, right_bound + 1), True, key=can_place_balls)
Correct interpretation:
result = bisect_left(range(left_bound, right_bound + 1), True, key=can_place_balls)
return left_bound + result - 1 # Subtract 1 to get the last valid distance
3. Greedy Placement Logic Error
When checking if balls can be placed, always place the first ball at the first available position. A common mistake is trying to optimize the first ball placement, which breaks the greedy approach.
Wrong:
def can_place_balls(min_distance):
balls_placed = 1 # Assuming first ball is placed
previous_position = position[0]
for i in range(1, len(position)): # Starting from index 1
if position[i] - previous_position >= min_distance:
previous_position = position[i]
balls_placed += 1
return balls_placed < m
Correct:
def can_place_balls(min_distance):
previous_position = -inf # Allow first ball to be placed anywhere
balls_placed = 0
for current_position in position: # Check all positions including first
if current_position - previous_position >= min_distance:
previous_position = current_position
balls_placed += 1
return balls_placed < m
4. Confusing Return Logic in Check Function
The check function's return value can be counterintuitive. It returns True
when we cannot place all balls (for bisect_left
to find the transition point), not when we can.
Common confusion:
# Returning True when placement succeeds (wrong for bisect_left) return balls_placed >= m # This inverts the logic needed
Correct:
# Return True when placement fails (correct for bisect_left) return balls_placed < m
Alternative Solution Using Standard Binary Search
To avoid the confusion with bisect_left
and inverted logic, here's a clearer implementation using standard binary search:
def maxDistance(self, position: List[int], m: int) -> int:
def can_place_all_balls(min_distance: int) -> bool:
"""Returns True if we CAN place all m balls with min_distance apart."""
balls_placed = 1
last_position = position[0]
for i in range(1, len(position)):
if position[i] - last_position >= min_distance:
balls_placed += 1
last_position = position[i]
if balls_placed == m:
return True
return False
position.sort()
left, right = 1, position[-1] - position[0]
result = 0
while left <= right:
mid = (left + right) // 2
if can_place_all_balls(mid):
result = mid # Update result when valid
left = mid + 1 # Try for larger distance
else:
right = mid - 1 # Distance too large, try smaller
return result
This approach is more intuitive as the check function returns True
for valid placements and we explicitly track the best result found.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!