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 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
  • Return cnt < m (returns True if we couldn't place all m 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 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: 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 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. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More