Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1515. Best Position for a Service Centre - Leetcode Solution

Problem Description

You are given an array positions where each element is a pair [x, y] representing the coordinates of a customer in a 2D plane. Your task is to determine the optimal position [cx, cy] to build a new service centre such that the sum of the Euclidean distances from the service centre to all customers is minimized.

  • The answer can be any real number coordinates [cx, cy].
  • There is exactly one unique solution.
  • Input size: 1 <= positions.length <= 50
  • Coordinates: positions[i].length == 2, 0 <= positions[i][0], positions[i][1] <= 100
  • Do not reuse elements; each customer is unique.

Thought Process

At first glance, this problem seems to ask for a "center" point that minimizes the total distance to all customers. The naive approach would be to check every possible point in the plane, but this is not feasible due to the infinite number of possible points and real-number precision.

Unlike the geometric median in 1-D (where the median minimizes sum of distances), in 2-D there is no closed-form solution for the geometric median. We need an efficient way to approximate this point. The centroid (arithmetic mean) minimizes the sum of squared distances, but not the sum of distances. Therefore, we need an iterative optimization technique.

The best-known approach for this is Weiszfeld's algorithm, which is a practical method to find the geometric median in higher dimensions.

Solution Approach

To solve this problem efficiently and accurately, we use Weiszfeld's algorithm, which iteratively improves the guess for the service centre location.

  1. Initialization:
    • Start with an initial guess, such as the centroid (mean of all x and y coordinates).
  2. Iterative Update:
    • At each iteration, update the guess using the formula:
      • For each customer position (xi, yi), compute the distance d = sqrt((x - xi)^2 + (y - yi)^2).
      • Compute the weighted sum of all positions, weighted by 1/d.
      • The new guess (x, y) is:
        • x = sum(xi / di) / sum(1 / di)
        • y = sum(yi / di) / sum(1 / di)
  3. Convergence:
    • Repeat the update until the change in position is less than a small epsilon (e.g., 1e-7), or a maximum number of iterations is reached.
  4. Return the Result:
    • The final (x, y) is the location that minimizes the sum of distances to all customers.

Design Choices:

  • Weiszfeld's algorithm is chosen because it's efficient (linear per iteration) and converges rapidly for small datasets.
  • Floating point arithmetic is used because the answer can be any real coordinate.
  • We use a small epsilon to determine convergence, ensuring accuracy.

Example Walkthrough

Sample Input:

positions = [[0,0],[1,0],[0,1]]
    
Step-by-step:
  1. Initialization:
    • Centroid: x = (0+1+0)/3 = 0.333..., y = (0+0+1)/3 = 0.333...
  2. First Iteration:
    • Compute distance from current guess to each customer.
    • For each, calculate 1/distance.
    • Update x and y using weighted sums.
  3. Subsequent Iterations:
    • Repeat the process; the guess moves closer to the optimal point.
    • After a few iterations, the change becomes negligible.
  4. Result:
    • The final coordinates are very close to (0.29289, 0.29289) (the geometric median).

This process ensures the sum of distances from the service centre to all customers is minimized.

Code Implementation

class Solution:
    def getMinDistSum(self, positions):
        import math
        n = len(positions)
        # Initial guess: centroid
        x = sum(p[0] for p in positions) / n
        y = sum(p[1] for p in positions) / n
        eps = 1e-7
        for _ in range(100):
            num_x = num_y = denom = 0
            for px, py in positions:
                d = math.hypot(x - px, y - py)
                if d < eps:
                    # If very close to a customer, use their position
                    x, y = px, py
                    break
                num_x += px / d
                num_y += py / d
                denom += 1 / d
            else:
                new_x = num_x / denom
                new_y = num_y / denom
                if abs(new_x - x) < eps and abs(new_y - y) < eps:
                    break
                x, y = new_x, new_y
        # Return the minimized sum of distances
        return sum(math.hypot(x - px, y - py) for px, py in positions)
      
class Solution {
public:
    double getMinDistSum(vector<vector<int>>& positions) {
        int n = positions.size();
        double x = 0, y = 0;
        for (auto& p : positions) {
            x += p[0];
            y += p[1];
        }
        x /= n;
        y /= n;
        const double eps = 1e-7;
        for (int iter = 0; iter < 100; ++iter) {
            double num_x = 0, num_y = 0, denom = 0;
            bool close = false;
            for (auto& p : positions) {
                double dx = x - p[0], dy = y - p[1];
                double d = sqrt(dx * dx + dy * dy);
                if (d < eps) {
                    x = p[0];
                    y = p[1];
                    close = true;
                    break;
                }
                num_x += p[0] / d;
                num_y += p[1] / d;
                denom += 1.0 / d;
            }
            if (close) continue;
            double new_x = num_x / denom;
            double new_y = num_y / denom;
            if (abs(new_x - x) < eps && abs(new_y - y) < eps) break;
            x = new_x;
            y = new_y;
        }
        double res = 0;
        for (auto& p : positions) {
            res += sqrt((x - p[0]) * (x - p[0]) + (y - p[1]) * (y - p[1]));
        }
        return res;
    }
};
      
class Solution {
    public double getMinDistSum(int[][] positions) {
        int n = positions.length;
        double x = 0, y = 0;
        for (int[] p : positions) {
            x += p[0];
            y += p[1];
        }
        x /= n;
        y /= n;
        final double eps = 1e-7;
        for (int iter = 0; iter < 100; ++iter) {
            double num_x = 0, num_y = 0, denom = 0;
            boolean close = false;
            for (int[] p : positions) {
                double dx = x - p[0], dy = y - p[1];
                double d = Math.hypot(dx, dy);
                if (d < eps) {
                    x = p[0];
                    y = p[1];
                    close = true;
                    break;
                }
                num_x += p[0] / d;
                num_y += p[1] / d;
                denom += 1.0 / d;
            }
            if (close) continue;
            double new_x = num_x / denom;
            double new_y = num_y / denom;
            if (Math.abs(new_x - x) < eps && Math.abs(new_y - y) < eps) break;
            x = new_x;
            y = new_y;
        }
        double res = 0;
        for (int[] p : positions) {
            res += Math.hypot(x - p[0], y - p[1]);
        }
        return res;
    }
}
      
var getMinDistSum = function(positions) {
    let n = positions.length;
    let x = 0, y = 0;
    for (let p of positions) {
        x += p[0];
        y += p[1];
    }
    x /= n;
    y /= n;
    const eps = 1e-7;
    for (let iter = 0; iter < 100; ++iter) {
        let num_x = 0, num_y = 0, denom = 0;
        let close = false;
        for (let p of positions) {
            let dx = x - p[0], dy = y - p[1];
            let d = Math.hypot(dx, dy);
            if (d < eps) {
                x = p[0];
                y = p[1];
                close = true;
                break;
            }
            num_x += p[0] / d;
            num_y += p[1] / d;
            denom += 1 / d;
        }
        if (close) continue;
        let new_x = num_x / denom;
        let new_y = num_y / denom;
        if (Math.abs(new_x - x) < eps && Math.abs(new_y - y) < eps) break;
        x = new_x;
        y = new_y;
    }
    let res = 0;
    for (let p of positions) {
        res += Math.hypot(x - p[0], y - p[1]);
    }
    return res;
};
      

Time and Space Complexity

  • Brute-force approach:
    • Would involve checking a grid of possible coordinates, leading to exponential or at least O(K*n) time, where K is the number of grid points (huge for real numbers).
  • Weiszfeld's algorithm (Optimized):
    • Each iteration: O(n), where n is the number of positions (customers).
    • Number of iterations: typically converges in less than 100 for small n.
    • Total time: O(n * I), where I is the number of iterations (constant for small n).
    • Space: O(1) extra (not counting input), since only a few variables are used.

Summary

The "Best Position for a Service Centre" problem asks for a location in 2D space minimizing the sum of Euclidean distances to a set of customers. While brute-force is infeasible due to infinite possible positions, Weiszfeld's algorithm efficiently finds the geometric median using simple, iterative updates. The approach is both elegant and practical, leveraging the properties of the Euclidean plane and converging quickly for small datasets. This solution highlights the power of iterative optimization for problems without closed-form solutions.