leetcode
leetcode 1351 ~ 1400
服务中心的最佳位置

服务中心的最佳位置

难度:

标签:

题目描述

一家快递公司希望在新城市建立新的服务中心。公司统计了该城市所有客户在二维地图上的坐标,并希望能够以此为依据为新的服务中心选址:使服务中心 到所有客户的欧几里得距离的总和最小

给你一个数组 positions ,其中 positions[i] = [xi, yi] 表示第 i 个客户在二维地图上的位置,返回到所有客户的 欧几里得距离的最小总和 。

换句话说,请你为服务中心选址,该位置的坐标 [xcentre, ycentre] 需要使下面的公式取到最小值:

与真实值误差在 10-5之内的答案将被视作正确答案。

 

示例 1:

输入:positions = [[0,1],[1,0],[1,2],[2,1]]
输出:4.00000
解释:如图所示,你可以选 [xcentre, ycentre] = [1, 1] 作为新中心的位置,这样一来到每个客户的距离就都是 1,所有距离之和为 4 ,这也是可以找到的最小值。

示例 2:

输入:positions = [[1,1],[3,3]]
输出:2.82843
解释:欧几里得距离可能的最小总和为 sqrt(2) + sqrt(2) = 2.82843

 

提示:

  • 1 <= positions.length <= 50
  • positions[i].length == 2
  • 0 <= xi, yi <= 100

代码结果

运行时间: 65 ms, 内存: 16.0 MB


/*
 思路:
 使用Java Stream API解决这个问题稍微有点棘手,因为Stream更适合处理无状态的操作。
 不过,我们仍然可以通过拆分一些计算步骤来实现梯度下降法。
*/
import java.util.stream.IntStream;

public class Solution {
    public double getMinDistSum(int[][] positions) {
        double x = IntStream.range(0, positions.length).mapToDouble(i -> positions[i][0]).average().orElse(0.0);
        double y = IntStream.range(0, positions.length).mapToDouble(i -> positions[i][1]).average().orElse(0.0);
        double learningRate = 0.1;
        double precision = 1e-7;
        double diff = Double.MAX_VALUE;
        while (diff > precision) {
            double gradX = 0.0, gradY = 0.0;
            for (int[] pos : positions) {
                double d = Math.sqrt((x - pos[0]) * (x - pos[0]) + (y - pos[1]) * (y - pos[1]));
                if (d != 0) {
                    gradX += (x - pos[0]) / d;
                    gradY += (y - pos[1]) / d;
                }
            }
            double newX = x - learningRate * gradX;
            double newY = y - learningRate * gradY;
            diff = Math.sqrt((newX - x) * (newX - x) + (newY - y) * (newY - y));
            x = newX;
            y = newY;
        }
        double result = 0.0;
        for (int[] pos : positions) {
            result += Math.sqrt((x - pos[0]) * (x - pos[0]) + (y - pos[1]) * (y - pos[1]));
        }
        return result;
    }
}

解释

方法:

题解采用了Weiszfeld算法来解决此问题,这是一种用于寻找一组点的几何中心(Fermat点)的有效方法,即最小化给定点到一个未知点的距离总和。算法流程如下:1. 初始化中心点为所有点坐标的算术平均值。2. 使用迭代方式,基于加权平均来调整中心点位置,权重为每个点到当前中心的逆距离。3. 检查新中心与旧中心的距离,若变化非常小则认为已收敛,结束迭代。4. 返回中心点到所有位置的欧几里得距离之和。

时间复杂度:

O(kn),其中n是位置数,k是迭代次数

空间复杂度:

O(n),其中n是输入位置的数量

代码细节讲解

🦆
Weiszfeld算法在迭代过程中若中心点恰好与某个点坐标完全相同,为什么会直接返回这个点作为解?
Weiszfeld算法是通过加权平均的方式调整中心点位置,权重为每个点到当前中心的逆距离。当算法迭代过程中的中心点与某个点完全相同时,该点到中心点的距离为0,导致这个点的权重变成无限大(因为1除以0是无限大)。这会使得其他所有点的相对权重变得微不足道,因此算法直接将这个点作为最终结果返回,因为继续迭代也会得到同样的结果。
🦆
在计算新的中心点时,为何选择使用每个点到当前中心的距离的逆作为权重,而不是其他可能的权重定义(如距离的平方的逆)?
在Weiszfeld算法中,使用每个点到当前中心的距离的逆作为权重,是为了确保距离中心点近的点对中心点位置的影响更大。这种权重选择反映了一个直观的想法:距离中心越近的点,其位置应该对中心点的确定有更大的影响。使用距离的逆而非距离的平方的逆或其他形式,是因为它提供了一个良好的平衡点,既能有效地拉近中心点与所有点之间的总距离,同时避免了对极端值过于敏感的问题。
🦆
迭代更新中心点时,设置的收敛阈值为1e-7是如何确定的?这个值对算法的精度和收敛速度有何影响?
收敛阈值1e-7的选择通常是基于实际应用中对精度的需求和计算资源的考量。这个值足够小,可以确保算法的解在实际应用中足够精确,而不会因为迭代终止过早而产生较大的误差。同时,这个值也不能太小,否则会导致算法需要更多的迭代次数,影响算法的执行效率。选择合适的收敛阈值是在算法的精度和效率之间寻找平衡的结果。
🦆
算法中提到最大迭代次数为1000次,这个限制是否基于特定的理论分析,还是经验值?如果迭代次数未达到收敛条件应如何处理?
最大迭代次数1000次通常是基于经验选择的,这个值通常足以让算法在多数实际情况下达到收敛。如果在1000次迭代后算法还没有达到预定义的收敛阈值,这可能表明输入数据具有某些特殊性质,使得算法难以快速收敛。在这种情况下,可以考虑调整收敛阈值,增加迭代次数,或者对输入数据进行预处理,以改善算法的收敛性能。

相关问题