Facebook Pixel

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 the n 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 Implementation

1from typing import List
2
3class Solution:
4    def maxDistance(self, position: List[int], m: int) -> int:
5        """
6        Find the maximum minimum distance between m balls placed in given positions.
7        Uses binary search template to find the first distance where placement fails.
8        """
9
10        def can_place_balls(min_distance: int) -> bool:
11            """
12            Check if we can place m balls with at least min_distance apart.
13            Returns True if placement is possible, False otherwise.
14            """
15            balls_placed = 1  # Place first ball at position[0]
16            previous_position = position[0]
17
18            for i in range(1, len(position)):
19                if position[i] - previous_position >= min_distance:
20                    balls_placed += 1
21                    previous_position = position[i]
22                    if balls_placed == m:
23                        return True
24
25            return False
26
27        # Sort positions to enable greedy placement
28        position.sort()
29
30        # Binary search using the standard template
31        # Feasible function: NOT can_place_balls(d) - true when we cannot place m balls
32        left, right = 1, position[-1] - position[0]
33        first_true_index = -1
34
35        while left <= right:
36            mid = (left + right) // 2
37
38            if not can_place_balls(mid):
39                # Feasible: cannot place m balls with this distance
40                first_true_index = mid
41                right = mid - 1
42            else:
43                # Not feasible: can still place m balls, try larger distance
44                left = mid + 1
45
46        # The answer is the largest distance where we CAN place all balls
47        if first_true_index == -1:
48            # All distances work, return the maximum
49            return position[-1] - position[0]
50        else:
51            # Return the distance just before placement becomes impossible
52            return first_true_index - 1
53
1class Solution {
2    private int[] positions;
3
4    /**
5     * Find the maximum minimum distance between any two balls in m baskets.
6     * Uses binary search template to find first distance where placement fails.
7     */
8    public int maxDistance(int[] position, int m) {
9        // Sort positions to enable greedy placement
10        Arrays.sort(position);
11        this.positions = position;
12
13        // Binary search using the standard template
14        // Feasible: cannot place m balls with this distance
15        int left = 1;
16        int right = position[position.length - 1] - position[0];
17        int firstTrueIndex = -1;
18
19        while (left <= right) {
20            int mid = left + (right - left) / 2;
21
22            if (!canPlaceBalls(mid, m)) {
23                // Feasible: cannot place m balls with this distance
24                firstTrueIndex = mid;
25                right = mid - 1;
26            } else {
27                // Not feasible: can still place m balls, try larger distance
28                left = mid + 1;
29            }
30        }
31
32        // Return the largest distance where we CAN place all balls
33        if (firstTrueIndex == -1) {
34            return position[position.length - 1] - position[0];
35        }
36        return firstTrueIndex - 1;
37    }
38
39    /**
40     * Check if we can place m balls with at least minDistance apart.
41     * Returns true if placement is possible.
42     */
43    private boolean canPlaceBalls(int minDistance, int m) {
44        int ballCount = 1;  // Place first ball at positions[0]
45        int previousPosition = positions[0];
46
47        for (int i = 1; i < positions.length; i++) {
48            if (positions[i] - previousPosition >= minDistance) {
49                ballCount++;
50                previousPosition = positions[i];
51                if (ballCount == m) {
52                    return true;
53                }
54            }
55        }
56
57        return false;
58    }
59}
60
1class Solution {
2public:
3    int maxDistance(vector<int>& position, int m) {
4        // Sort positions to enable greedy placement
5        sort(position.begin(), position.end());
6
7        // Lambda to check if we can place m balls with minDist apart
8        auto canPlaceBalls = [&](int minDist) -> bool {
9            int ballCount = 1;  // Place first ball at position[0]
10            int previousPosition = position[0];
11
12            for (int i = 1; i < position.size(); i++) {
13                if (position[i] - previousPosition >= minDist) {
14                    ballCount++;
15                    previousPosition = position[i];
16                    if (ballCount == m) {
17                        return true;
18                    }
19                }
20            }
21            return false;
22        };
23
24        // Binary search using the standard template
25        // Feasible: cannot place m balls with this distance
26        int left = 1;
27        int right = position.back() - position.front();
28        int firstTrueIndex = -1;
29
30        while (left <= right) {
31            int mid = left + (right - left) / 2;
32
33            if (!canPlaceBalls(mid)) {
34                // Feasible: cannot place m balls with this distance
35                firstTrueIndex = mid;
36                right = mid - 1;
37            } else {
38                // Not feasible: can still place m balls, try larger distance
39                left = mid + 1;
40            }
41        }
42
43        // Return the largest distance where we CAN place all balls
44        if (firstTrueIndex == -1) {
45            return position.back() - position.front();
46        }
47        return firstTrueIndex - 1;
48    }
49};
50
1/**
2 * Finds the maximum minimum distance between any two balls placed in positions.
3 * Uses binary search template to find first distance where placement fails.
4 */
5function maxDistance(position: number[], m: number): number {
6    // Sort positions to enable greedy placement
7    position.sort((a, b) => a - b);
8
9    /**
10     * Check if we can place m balls with at least minDistance apart.
11     * Returns true if placement is possible.
12     */
13    const canPlaceBalls = (minDistance: number): boolean => {
14        let ballCount = 1;  // Place first ball at position[0]
15        let previousPosition = position[0];
16
17        for (let i = 1; i < position.length; i++) {
18            if (position[i] - previousPosition >= minDistance) {
19                ballCount++;
20                previousPosition = position[i];
21                if (ballCount === m) {
22                    return true;
23                }
24            }
25        }
26
27        return false;
28    };
29
30    // Binary search using the standard template
31    // Feasible: cannot place m balls with this distance
32    let left = 1;
33    let right = position[position.length - 1] - position[0];
34    let firstTrueIndex = -1;
35
36    while (left <= right) {
37        const mid = Math.floor((left + right) / 2);
38
39        if (!canPlaceBalls(mid)) {
40            // Feasible: cannot place m balls with this distance
41            firstTrueIndex = mid;
42            right = mid - 1;
43        } else {
44            // Not feasible: can still place m balls, try larger distance
45            left = mid + 1;
46        }
47    }
48
49    // Return the largest distance where we CAN place all balls
50    if (firstTrueIndex === -1) {
51        return position[position.length - 1] - position[0];
52    }
53    return firstTrueIndex - 1;
54}
55

Solution Approach

The solution implements binary search using the standard template combined with a greedy verification function.

1. Sort the positions array: First, we sort the basket positions to enable the greedy placement strategy.

2. Define the feasible function: We define feasible(d) as "we CANNOT place m balls with minimum distance d". This creates a pattern:

[false, false, ..., false, true, true, ..., true]

We want to find the first distance where placement becomes impossible.

3. Greedy verification: To check if we can place m balls with distance d:

  • Place the first ball at position[0]
  • Greedily place each subsequent ball at the first position that is at least d away from the previous ball
  • Count how many balls we can place
  • Return count < m (true if we cannot place all m balls)

4. Binary search using the template:

left, right = 1, position[-1] - position[0]
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if not can_place_balls(mid):  # feasible: cannot place m balls
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

5. Calculate the answer: The answer is first_true_index - 1 (the largest distance where we CAN place all balls). If first_true_index == -1, all distances in range work, so return the maximum distance.

Time Complexity: O(n log n + n log M) where n is the number of baskets and M is the position range.

Space Complexity: O(log n) for sorting.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example 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: Understand the feasible function feasible(d) = "cannot place 3 balls with minimum distance d"

  • d=1: can place at 1,2,3 → false (can place)
  • d=2: can place at 1,3,7 → false (can place)
  • d=3: can place at 1,4,7 → false (can place)
  • d=4: can only place at 1,7 (2 balls) → true (cannot place 3)
  • d=5: can only place at 1,7 (2 balls) → true (cannot place 3)
  • d=6: can only place at 1,7 (2 balls) → true (cannot place 3)

Pattern: [false, false, false, true, true, true] → first_true_index = 4

Step 3: Binary search using template Initialize: left = 1, right = 6, first_true_index = -1

Iteration 1: left=1, right=6

  • mid = (1 + 6) // 2 = 3
  • Can place 3 balls with distance 3? Place at 1, then 4, then 7 → Yes!
  • can_place_balls(3) = true, so feasible(3) = false
  • left = mid + 1 = 4

Iteration 2: left=4, right=6

  • mid = (4 + 6) // 2 = 5
  • Can place 3 balls with distance 5? Place at 1, then need 6+, only 7 works → Only 2 balls
  • can_place_balls(5) = false, so feasible(5) = true
  • first_true_index = 5, right = mid - 1 = 4

Iteration 3: left=4, right=4

  • mid = (4 + 4) // 2 = 4
  • Can place 3 balls with distance 4? Place at 1, then need 5+, only 7 works → Only 2 balls
  • can_place_balls(4) = false, so feasible(4) = true
  • first_true_index = 4, right = mid - 1 = 3

Loop ends as left > right (4 > 3).

Step 4: Calculate the answer

  • first_true_index = 4 (first distance where we cannot place 3 balls)
  • Answer = first_true_index - 1 = 3 (last distance where we can place 3 balls)

Final Answer: 3

The optimal placement is at positions 1, 4, and 7, giving minimum distance 3.

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 the position array, where n is the length of the position array
  • O(n × log M) for the binary search operation, where:
    • The binary search runs O(log M) iterations, with M = position[-1] - 1 being the search range (from 1 to the maximum position value)
    • Each iteration calls the check function, which iterates through all n positions in O(n) time
    • Combined: O(n × log M)

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 uses O(1) additional space with only a few variables (prev, cnt, curr)
  • The binary search itself uses O(1) space for variables l and r
  • 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. Using Wrong Binary Search Template Variant

A common mistake is using while left < right with left = mid (finding last true) instead of the standard template with first_true_index.

Wrong variant:

while left < right:
    mid = (left + right + 1) // 2  # Ceiling to avoid infinite loop
    if can_place_balls(mid):
        left = mid
    else:
        right = mid - 1
return left

Correct template:

first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if not can_place_balls(mid):  # Invert: true when cannot place
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
return first_true_index - 1 if first_true_index != -1 else right

2. Incorrect Binary Search Bounds

Setting right bound as position[-1] (a position) instead of position[-1] - position[0] (maximum distance).

Wrong:

right = position[-1]  # This is a position, not a distance

Correct:

right = position[-1] - position[0]  # Maximum possible distance

3. Off-by-One Error in Result Calculation

When using first_true_index (first distance where placement fails), the answer is first_true_index - 1.

Wrong:

return first_true_index  # Returns first invalid distance!

Correct:

return first_true_index - 1  # Last valid distance

4. Not Handling first_true_index == -1

If all distances allow placement, first_true_index stays -1. Must handle this case.

Solution:

if first_true_index == -1:
    return position[-1] - position[0]  # All distances work
return first_true_index - 1

5. Greedy Placement Starting Position

Always place the first ball at position[0]. Don't use -inf which complicates logic.

Simpler approach:

def can_place_balls(min_distance):
    balls_placed = 1  # First ball at position[0]
    prev = position[0]

    for i in range(1, len(position)):
        if position[i] - prev >= min_distance:
            balls_placed += 1
            prev = position[i]

    return balls_placed >= m

Template-Compliant Solution:

def maxDistance(self, position: List[int], m: int) -> int:
    def can_place_balls(min_distance: int) -> bool:
        balls_placed = 1
        prev = position[0]
        for i in range(1, len(position)):
            if position[i] - prev >= min_distance:
                balls_placed += 1
                prev = position[i]
        return balls_placed >= m

    position.sort()
    left, right = 1, position[-1] - position[0]
    first_true_index = -1

    while left <= right:
        mid = (left + right) // 2
        if not can_place_balls(mid):
            first_true_index = mid
            right = mid - 1
        else:
            left = mid + 1

    if first_true_index == -1:
        return position[-1] - position[0]
    return first_true_index - 1
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More