一家快递公司希望在新城市建立新的服务中心。公司统计了该城市所有客户在二维地图上的坐标,并希望能够以此为依据为新的服务中心选址:使服务中心到所有客户的欧几里得距离的总和最小  。
给你一个数组 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 
 
前言 比起普通的算法题,本题更像是机器学习岗位面试中的一道数学题。题目中给定的函数是一个凸函数(convex function),因此这是一个凸优化问题,「局部最小值」就等于「全局最小值」。
需要注意的是,最终的结果 (x_c, y_c) 是给定的 {x_i\ 和 {y_i\ 的「几何中位数」。几何中位数并没有解析解,我们无法直接求出「全局最小值」。但我们可以使用求解「局部最小值」的方法,得到的结果就等于「全局最小值」。
为了叙述方便,本题解用 (x_c, y_c) 表示题目描述中的 (x_{center}, y_{center})。
 
本题解会简单科普一些常用的求解「局部最小值」的算法。由于大部分算法为迭代法,因此不给出复杂度分析。凸函数的证明过程可参考文末的「凸函数证明」部分。
方法一:梯度下降法 「梯度下降法」是机器学习中常用的一种求解局部最小值的算法。对于给定的点 (x, y),它的梯度方向是函数值上升最快的方向,因此梯度反向就是函数值下降最快的方向。本题中需要优化的函数为:
f(x_c, y_c) = \sum_{i=0}^{n-1} \sqrt{(x_c-x_i)^2 + (y_c-y_i)^2}
它的导数为:
\left{ \begin{aligned} \partial f}{\partial x} = \sum_{i=0}^{n-1} x_c-x_i}{\sqrt{(x_c-x_i)^2 + (y_c-y_i)^2} } \ \partial f}{\partial y} = \sum_{i=0}^{n-1} y_c-y_i}{\sqrt{(x_c-x_i)^2 + (y_c-y_i)^2} } \end{aligned} \right.
那么梯度反向 -\nabla f = (-\dfrac{\partial f}{\partial x}, -\dfrac{\partial f}{\partial y})。我们从一个初始点 (x_{start}, y_{start}) 开始进行迭代,每次令
\left{ \begin{aligned} x’{start} = x {start} - \alpha \cdot \partial f}{\partial x} \ y’{start} = y {start} - \alpha \cdot \partial f}{\partial y} \end{aligned} \right.
得到一个新的点 (x’{start}, y’ {start}),其中 \alpha 为学习率(learning rate)。当迭代了一定次数之后,当前的点会非常接近真正的最小值点,如果我们的学习速率保持不变,迭代的结果将会在最小值点的周围来回「震荡」,无法继续接近最小值点。因此,我们需要设置学习率衰减(learning rate decay),在迭代的过程中逐步减小学习率,向最小值点逼近。
我们也可以使用机器学习中的一些技巧,例如「小批量梯度下降法」等,每次只对一批 (x_i, y_i) 进行求导并更新答案。在下面的代码中,我们令:
初始点 (x_{start}, y_{start}) = \left(\dfrac{\sum_{i=0}^{n-1} x_i}{n}, \dfrac{\sum_{i=0}^{n-1} y_i}{n} \right),即算术平均值;
 
学习率 \alpha = 1;
 
学习率衰减 \eta = 10^{-3;
 
当 (x_{start}, y_{start}) 与 (x’{start}, y’ {start}) 的距离小于 10^{-7 时结束迭代。
 
 
对于下面的代码,当批大小 batchSize} = n,为「批量梯度下降法」,可以通过本题;当 n=8/16 等常用值时,为「小批量梯度下降法」,由于具有随机性,可能得到的解并不精确,但有较高概率可以通过本题。
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 class  Solution  {public :    double  getMinDistSum (vector<vector<int >>& positions)   {         double  eps = 1e-7 ;         double  alpha = 1 ;         double  decay = 1e-3 ;                  int  n = positions.size ();                  int  batchSize = n;                  double  x = 0.0 , y = 0.0 ;         for  (const  auto & pos: positions) {             x += pos[0 ];             y += pos[1 ];         }         x /= n;         y /= n;                           auto  getDist = [&](double  xc, double  yc) {             double  ans = 0 ;             for  (const  auto & pos: positions) {                 ans += sqrt ((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));             }             return  ans;         };                  mt19937 gen{random_device{}()};         while  (true ) {                          shuffle (positions.begin (), positions.end (), gen);             double  xPrev = x;             double  yPrev = y;             for  (int  i = 0 ; i < n; i += batchSize) {                 int  j = min (i + batchSize, n);                 double  dx = 0.0 , dy = 0.0 ;                                  for  (int  k = i; k < j; ++k) {                     const  auto & pos = positions[k];                     dx += (x - pos[0 ]) / (sqrt ((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps);                     dy += (y - pos[1 ]) / (sqrt ((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps);                 }                 x -= alpha * dx;                 y -= alpha * dy;                                  alpha *= (1.0  - decay);             }                                       if  (sqrt ((x - xPrev) * (x - xPrev) + (y - yPrev) * (y - yPrev)) < eps) {                 break ;             }         }                  return  getDist (x, y);     } }; 
 
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 class  Solution  {    public  double  getMinDistSum (int [][] positions)  {         double  eps  =  1e-7 ;         double  alpha  =  1 ;         double  decay  =  1e-3 ;                  int  n  =  positions.length;                  int  batchSize  =  n;                  double  x  =  0.0 , y = 0.0 ;         for  (int [] pos : positions) {             x += pos[0 ];             y += pos[1 ];         }         x /= n;         y /= n;                  while  (true ) {                          shuffle(positions);             double  xPrev  =  x;             double  yPrev  =  y;             for  (int  i  =  0 ; i < n; i += batchSize) {                 int  j  =  Math.min(i + batchSize, n);                 double  dx  =  0.0 , dy = 0.0 ;                                  for  (int  k  =  i; k < j; ++k) {                     int [] pos = positions[k];                     dx += (x - pos[0 ]) / (Math.sqrt((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps);                     dy += (y - pos[1 ]) / (Math.sqrt((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps);                 }                 x -= alpha * dx;                 y -= alpha * dy;                                  alpha *= (1.0  - decay);             }                                       if  (Math.sqrt((x - xPrev) * (x - xPrev) + (y - yPrev) * (y - yPrev)) < eps) {                 break ;             }         }                  return  getDist(x, y, positions);     }     public  void  shuffle (int [][] positions)  {         Random  rand  =  new  Random ();         int  n  =  positions.length;         for  (int  i  =  0 ; i < n; i++) {             int  x  =  positions[i][0 ], y = positions[i][1 ];             int  randIndex  =  rand.nextInt(n);             positions[i][0 ] = positions[randIndex][0 ];             positions[i][1 ] = positions[randIndex][1 ];             positions[randIndex][0 ] = x;             positions[randIndex][1 ] = y;         }     }          public  double  getDist (double  xc, double  yc, int [][] positions)  {         double  ans  =  0 ;         for  (int [] pos : positions) {             ans += Math.sqrt((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));         }         return  ans;     } } 
 
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class  Solution :    def  getMinDistSum (self, positions: List [List [int ]] ) -> float :         eps = 1e-7          alpha = 1.0          decay = 1e-3          n = len (positions)                  batchSize = n         x = sum (pos[0 ] for  pos in  positions) / n         y = sum (pos[1 ] for  pos in  positions) / n                           getDist = lambda  xc, yc: sum (((x - xc) ** 2  + (y - yc) ** 2 ) ** 0.5  for  x, y in  positions)                  while  True :                          random.shuffle(positions)             xPrev, yPrev = x, y             for  i in  range (0 , n, batchSize):                 j = min (i + batchSize, n)                 dx, dy = 0.0 , 0.0                                   for  k in  range (i, j):                     pos = positions[k]                     dx += (x - pos[0 ]) / (sqrt((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps)                     dy += (y - pos[1 ]) / (sqrt((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps)                                  x -= alpha * dx                 y -= alpha * dy                                  alpha *= (1.0  - decay)                                       if  ((x - xPrev) ** 2  + (y - yPrev) ** 2 ) ** 0.5  < eps:                 break          return  getDist(x, y) 
 
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 double  getDist (int ** positions, int  positionsSize, double  xc, double  yc)  {    double  ans = 0 ;     for  (int  i = 0 ; i < positionsSize; i++) {         int * pos = positions[i];         ans += sqrt ((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));     }     return  ans; }; void  shuffle (int ** positions, int  positionsSize)  {    for  (int  i = positionsSize - 1 ; i >= 0 ; i--) {         int  add = rand() % (i + 1 );         int  tmp[2 ] = {positions[add][0 ], positions[add][1 ]};         positions[add][0 ] = positions[i][0 ];         positions[add][1 ] = positions[i][1 ];         positions[i][0 ] = tmp[0 ];         positions[i][1 ] = tmp[1 ];     } } double  getMinDistSum (int ** positions, int  positionsSize, int * positionsColSize)  {    double  eps = 1e-7 ;     double  alpha = 1 ;     double  decay = 1e-3 ;     int  batchSize = positionsSize;     double  x = 0.0 , y = 0.0 ;     for  (int  i = 0 ; i < positionsSize; i++) {         int * pos = positions[i];         x += pos[0 ];         y += pos[1 ];     }     x /= positionsSize;     y /= positionsSize;     srand(time(0 ));     while  (true ) {                  shuffle(positions, positionsSize);         double  xPrev = x;         double  yPrev = y;         for  (int  i = 0 ; i < positionsSize; i += batchSize) {             int  j = fmin(i + batchSize, positionsSize);             double  dx = 0.0 , dy = 0.0 ;                          for  (int  k = i; k < j; ++k) {                 int * pos = positions[k];                 dx += (x - pos[0 ]) / (sqrt ((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps);                 dy += (y - pos[1 ]) / (sqrt ((x - pos[0 ]) * (x - pos[0 ]) + (y - pos[1 ]) * (y - pos[1 ])) + eps);             }             x -= alpha * dx;             y -= alpha * dy;                          alpha *= (1.0  - decay);         }                  if  (sqrt ((x - xPrev) * (x - xPrev) + (y - yPrev) * (y - yPrev)) < eps) {             break ;         }     }     return  getDist(positions, positionsSize, x, y); } 
 
方法二:爬山法 如果给定的凸函数很难进行求导(或者读者懒得求导)怎么办?注意到梯度反向 -\nabla f = (-\dfrac{\partial f}{\partial x}, -\dfrac{\partial f}{\partial y}) 实际上可以拆分成:
\begin{aligned} (-\dfrac{\partial f}{\partial x}, -\dfrac{\partial f}{\partial y}) &= (-\dfrac{\partial f}{\partial x}, 0) + (0, -\dfrac{\partial f}{\partial y}) \ &= -\dfrac{\partial f}{\partial x} \cdot (1, 0)  -\dfrac{\partial f}{\partial y} \cdot (0, 1) \end{aligned}
即我们「横向」移动若干个单位长度,「纵向」移动若干个单位长度,也可以得到和梯度下降一样的结果。因此我们只需要考虑四个方向,即单位向量 (1, 0),(-1, 0),(0, 1) 和 (0, -1)。
初始时,我们选择一个「步长」step,表示每次移动的距离。如果我们当前在位置 (x, y),我们就依次枚举四个方向 (d_x, d_y),并判断 (x + \text{step} \cdot d_x, y + \text{step} \cdot d_y) 对应的函数值是否更小。如果找到一个满足要求的方向,我们就进行移动;否则说明我们当前的「步长」较大,直接越过了最值点,因此调整步长为原来的一半,直到步长小于给定的阈值 \epsilon。
在下面的代码中,我们令:
就可以通过本题。
这种方法叫做「爬山法」:如果我们将一个凸函数倒过来看,它会形成一个类似「山峰」的形状。在我们攀登顶峰(找到最大值点)的过程中,我们可能并不知道顶峰在哪里。但如果我们每一步都环顾四周,走向更高的地方(检查四个方向的函数值是否更大),那么我们最终总能到达山峰。
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class  Solution  {private :    static  constexpr  int  dirs[4 ][2 ] = { {-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 } }; public :    double  getMinDistSum (vector<vector<int >>& positions)   {         double  eps = 1e-7 ;         double  step = 1 ;         double  decay = 0.5 ;                  int  n = positions.size ();                  double  x = 0.0 , y = 0.0 ;         for  (const  auto & pos: positions) {             x += pos[0 ];             y += pos[1 ];         }         x /= n;         y /= n;                           auto  getDist = [&](double  xc, double  yc) {             double  ans = 0 ;             for  (const  auto & pos: positions) {                 ans += sqrt ((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));             }             return  ans;         };                  while  (step > eps) {             bool  modified = false ;             for  (int  i = 0 ; i < 4 ; ++i) {                 double  xNext = x + step * dirs[i][0 ];                 double  yNext = y + step * dirs[i][1 ];                 if  (getDist (xNext, yNext) < getDist (x, y)) {                     x = xNext;                     y = yNext;                     modified = true ;                     break ;                 }             }             if  (!modified) {                 step *= (1.0  - decay);             }         }                  return  getDist (x, y);     } }; 
 
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class  Solution  {    private  static  int [][] dirs = { {-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 } };     public  double  getMinDistSum (int [][] positions)  {         double  eps  =  1e-7 ;         double  step  =  1 ;         double  decay  =  0.5 ;                  int  n  =  positions.length;                  double  x  =  0.0 , y = 0.0 ;         for  (int [] pos : positions) {             x += pos[0 ];             y += pos[1 ];         }         x /= n;         y /= n;                  while  (step > eps) {             boolean  modified  =  false ;             for  (int  i  =  0 ; i < 4 ; ++i) {                 double  xNext  =  x + step * dirs[i][0 ];                 double  yNext  =  y + step * dirs[i][1 ];                 if  (getDist(xNext, yNext, positions) < getDist(x, y, positions)) {                     x = xNext;                     y = yNext;                     modified = true ;                     break ;                 }             }             if  (!modified) {                 step *= (1.0  - decay);             }         }                  return  getDist(x, y, positions);     }          public  double  getDist (double  xc, double  yc, int [][] positions)  {         double  ans  =  0 ;         for  (int [] pos : positions) {             ans += Math.sqrt((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));         }         return  ans;     } } 
 
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class  Solution :    def  getMinDistSum (self, positions: List [List [int ]] ) -> float :         dirs = [(-1 , 0 ), (1 , 0 ), (0 , -1 ), (0 , 1 )]         eps = 1e-7          step = 1.0          decay = 0.5          n = len (positions)         x = sum (pos[0 ] for  pos in  positions) / n         y = sum (pos[1 ] for  pos in  positions) / n                           getDist = lambda  xc, yc: sum (((x - xc) ** 2  + (y - yc) ** 2 ) ** 0.5  for  x, y in  positions)                  while  step > eps:             modified = False              for  dx, dy in  dirs:                 xNext = x + step * dx                 yNext = y + step * dy                 if  getDist(xNext, yNext) < getDist(x, y):                     x, y = xNext, yNext                     modified = True                      break              if  not  modified:                 step *= (1.0  - decay)         return  getDist(x, y) 
 
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include  <math.h>  #include  <stdbool.h>  #include  <string.h>  double  getDist (int ** positions, int  positionsSize, double  xc, double  yc)  {    double  ans = 0 ;     for  (int  i = 0 ; i < positionsSize; i++) {         int * pos = positions[i];         ans += sqrt ((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));     }     return  ans; }; const  int  dirs[4 ][2 ] = { {-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 } };double  getMinDistSum (int ** positions, int  positionsSize, int * positionsColSize)  {    double  eps = 1e-7 ;     double  step = 1 ;     double  decay = 0.5 ;     int  batchSize = positionsSize;     double  x = 0.0 , y = 0.0 ;     for  (int  i = 0 ; i < positionsSize; i++) {         int * pos = positions[i];         x += pos[0 ];         y += pos[1 ];     }     x /= positionsSize;     y /= positionsSize;     while  (step > eps) {         bool  modified = false ;         for  (int  i = 0 ; i < 4 ; ++i) {             double  xNext = x + step * dirs[i][0 ];             double  yNext = y + step * dirs[i][1 ];             if  (getDist(positions, positionsSize, xNext, yNext) < getDist(positions, positionsSize, x, y)) {                 x = xNext;                 y = yNext;                 modified = true ;                 break ;             }         }         if  (!modified) {             step *= (1.0  - decay);         }     }     return  getDist(positions, positionsSize, x, y); } 
 
方法三:三分查找 除了寻找下降的方向之外,我们也可以使用「三分查找」,逐步缩小范围,找到最小值点。
考虑一个 \mathbb{R 上的凸函数 y = f(x),它的定义域为 [L, R],我们可以用三分查找的方式将不可能包含最小值点的范围排除:
我们取两个点 x_1, x_2,满足 L < x_1 < x_2 < R,记 y_1 = f(x_1),y_2 = f(x_2);
 
如果 y_1 > y_2,那么最小值点一定不在 [L, x_1] 内;如果 y_1 < y_2,那么最小值点一定不在 [x_2, R] 内。
 
 
证明也很容易,只需要用到凸函数的定义。假设最小值点在 [L, x_1] 内,记为 x_m。由于 y_1 > y_2,那么最小值点显然不为 x_1,即 x_1 \neq x_m。因此有 x_m < x_1 < x_2 且 f(x_m) < f(x_1),f(x_2) < f(x_1)。由于 f 是凸函数,使用定比分点可以得到:
x_2-x_1}{x_2-x_m} f(x_m) + x_1-x_m}{x_2-x_m} f(x_2) \geq f\left( x_2-x_1}{x_2-x_m} \cdot x_m + x_1-x_m}{x_2-x_m} \cdot x_2 \right)= f(x_1)
然而:
x_2-x_1}{x_2-x_m} f(x_m) + x_1-x_m}{x_2-x_m} f(x_2) < x_2-x_1}{x_2-x_m} f(x_1) + x_1-x_m}{x_2-x_m} f(x_1) = f(x_1)
产生了矛盾!因此最小值点一定不在 [L, x_1] 内。对于 y_1 < y_2 的情况同理。
我们可以取 x_1, x_2 为 [L, R] 的三等分点,即:
\left{ \begin{aligned} & x_1 = 2L + R}{3} \ & x_2 = L + 2R}{3} \end{aligned} \right.
这样我们每次就可以排除三分之一的定义域。当定义域的宽度 R-L 收敛至给定的阈值 \epsilon 时,就可以结束三分查找。此时 L 和 R 足够接近,可以将 [L, R] 内的所有值都视作极值点。
题目中的函数 f(x_c, y_c) 是二元函数,我们可以使用「三分查找」套「三分查找」的方法。外层的三分查找用来确定 x_c 的范围,每次三分查找时,取定义域 [x_L, x_R] 中的两个三等分点 x_1, x_2,并分别固定 x_c 的值为 x_1 和 x_2,对 y_c 进行三分查找。将得到的最优解进行比较,以此选择忽略 [x_L, x_1] 或 [x_2, x_R]。
每一层的三分查找都会在定义域的宽度小于给定的阈值 \epsilon 时结束。在下面的代码中,我们令:
区间宽度阈值 \epsilon = 10^{-7; 
 
就可以通过本题。
[sol3-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class  Solution  {public :    double  getMinDistSum (vector<vector<int >>& positions)   {         double  eps = 1e-7 ;                  auto  getDist = [&](double  xc, double  yc) {             double  ans = 0 ;             for  (const  auto & pos: positions) {                 ans += sqrt ((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));             }             return  ans;         };                  auto  checkOptimal = [&](double  xc) {             double  yLeft = 0.0 , yRight = 100.0 ;             while  (yRight - yLeft > eps) {                 double  yFirst = (yLeft + yLeft + yRight) / 3 ;                 double  ySecond = (yLeft + yRight + yRight) / 3 ;                 if  (getDist (xc, yFirst) < getDist (xc, ySecond)) {                     yRight = ySecond;                 }                 else  {                     yLeft = yFirst;                 }             }             return  getDist (xc, yLeft);         };                  double  xLeft = 0.0 , xRight = 100.0 ;         while  (xRight - xLeft > eps) {                          double  xFirst = (xLeft + xLeft + xRight) / 3 ;                          double  xSecond = (xLeft + xRight + xRight) / 3 ;             if  (checkOptimal (xFirst) < checkOptimal (xSecond)) {                 xRight = xSecond;             }             else  {                 xLeft = xFirst;             }         }         return  checkOptimal (xLeft);     } }; 
 
[sol3-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class  Solution  {    public  double  getMinDistSum (int [][] positions)  {         double  eps  =  1e-7 ;         double  xLeft  =  0.0 , xRight = 100.0 ;         while  (xRight - xLeft > eps) {                          double  xFirst  =  (xLeft + xLeft + xRight) / 3 ;                          double  xSecond  =  (xLeft + xRight + xRight) / 3 ;             if  (checkOptimal(xFirst, positions, eps) < checkOptimal(xSecond, positions, eps)) {                 xRight = xSecond;             } else  {                 xLeft = xFirst;             }         }         return  checkOptimal(xLeft, positions, eps);     }          public  double  getDist (double  xc, double  yc, int [][] positions)  {         double  ans  =  0 ;         for  (int [] pos : positions) {             ans += Math.sqrt((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));         }         return  ans;     }          public  double  checkOptimal (double  xc, int [][] positions, double  eps)  {         double  yLeft  =  0.0 , yRight = 100.0 ;         while  (yRight - yLeft > eps) {             double  yFirst  =  (yLeft + yLeft + yRight) / 3 ;             double  ySecond  =  (yLeft + yRight + yRight) / 3 ;             if  (getDist(xc, yFirst, positions) < getDist(xc, ySecond, positions)) {                 yRight = ySecond;             } else  {                 yLeft = yFirst;             }         }         return  getDist(xc, yLeft, positions);     }     } 
 
[sol3-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class  Solution :    def  getMinDistSum (self, positions: List [List [int ]] ) -> float :         eps = 1e-7                   getDist = lambda  xc, yc: sum (((x - xc) ** 2  + (y - yc) ** 2 ) ** 0.5  for  x, y in  positions)                  def  checkOptimal (xc: float  ) -> float :             yLeft, yRight = 0.0 , 100.0              while  yRight - yLeft > eps:                 yFirst = (yLeft + yLeft + yRight) / 3                  ySecond = (yLeft + yRight + yRight) / 3                  if  getDist(xc, yFirst) < getDist(xc, ySecond):                     yRight = ySecond                 else :                     yLeft = yFirst             return  getDist(xc, yLeft)                  xLeft, xRight = 0.0 , 100.0          while  xRight - xLeft > eps:                          xFirst = (xLeft + xLeft + xRight) / 3                           xSecond = (xLeft + xRight + xRight) / 3              if  checkOptimal(xFirst) < checkOptimal(xSecond):                 xRight = xSecond             else :                 xLeft = xFirst         return  checkOptimal(xLeft) 
 
[sol3-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 double  eps = 1e-7 ;double  getDist (int ** positions, int  positionsSize, double  xc, double  yc)  {    double  ans = 0 ;     for  (int  i = 0 ; i < positionsSize; i++) {         int * pos = positions[i];         ans += sqrt ((pos[0 ] - xc) * (pos[0 ] - xc) + (pos[1 ] - yc) * (pos[1 ] - yc));     }     return  ans; }; double  checkOptimal (int ** positions, int  positionsSize, double  xc)  {    double  yLeft = 0.0 , yRight = 100.0 ;     while  (yRight - yLeft > eps) {         double  yFirst = (yLeft + yLeft + yRight) / 3 ;         double  ySecond = (yLeft + yRight + yRight) / 3 ;         if  (getDist(positions, positionsSize, xc, yFirst) < getDist(positions, positionsSize, xc, ySecond)) {             yRight = ySecond;         } else  {             yLeft = yFirst;         }     }     return  getDist(positions, positionsSize, xc, yLeft); }; double  getMinDistSum (int ** positions, int  positionsSize, int * positionsColSize)  {    int  batchSize = positionsSize;     double  x = 0.0 , y = 0.0 ;     for  (int  i = 0 ; i < positionsSize; i++) {         int * pos = positions[i];         x += pos[0 ];         y += pos[1 ];     }     x /= positionsSize;     y /= positionsSize;     double  xLeft = 0.0 , xRight = 100.0 ;     while  (xRight - xLeft > eps) {                  double  xFirst = (xLeft + xLeft + xRight) / 3 ;                  double  xSecond = (xLeft + xRight + xRight) / 3 ;         if  (checkOptimal(positions, positionsSize, xFirst) < checkOptimal(positions, positionsSize, xSecond)) {             xRight = xSecond;         } else  {             xLeft = xFirst;         }     }     return  checkOptimal(positions, positionsSize, xLeft); } 
 
凸函数证明 给定 x_0, \cdots, x_{n-1} \in \mathbb{R 以及 y_0, \cdots, y_{n-1} \in \mathbb{R,证明
f(x, y) = \sum_{i=0}^{n-1} \sqrt{(x-x_i)^2 + (y-y_i)^2}
是 \mathbb{R}^2 上的凸函数(convex function)。
证明 
记 f_i(x, y) = \sqrt{(x-x_i)^2 + (y-y_i)^2,显然 f_i(x, y) 连续可导。一阶导数为:
\left{ \begin{aligned} \partial f_i}{\partial x} = x-x_i}{\sqrt{(x-x_i)^2 + (y-y_i)^2} } \ \partial f_i}{\partial y} = y-y_i}{\sqrt{(x-x_i)^2 + (y-y_i)^2} } \end{aligned} \right.
二阶导数为:
\left{ \begin{aligned} & \partial^2 f_i}{\partial x^2} = (y-y_i)^2}{\big((x-x_i)^2 + (y-y_i)^2\big)^{3/2} }} \ & \partial^2 f_i}{\partial y^2} = (x-x_i)^2}{\big((x-x_i)^2 + (y-y_i)^2\big)^{3/2} }} \ & \partial^2 f_i}{\partial x \partial y} = \partial^2 f_i}{\partial y \partial x} = -(x-x_i)(y-y_i)}{\big((x-x_i)^2 + (y-y_i)^2\big)^{3/2} }} \end{aligned} \right.
对应的 Hessian 矩阵
H(f_i) = \left[ \begin{array}{cc} \dfrac{\partial^2 f_i}{\partial x^2} & \dfrac{\partial^2 f_i}{\partial x \partial y} \ \ \dfrac{\partial^2 f_i}{\partial y \partial x} & \dfrac{\partial^2 f_i}{\partial y^2} \end{array} \right]
的主子式
\left{ \begin{aligned} & \partial^2 f_i}{\partial x^2} \geq 0 \ \ & \partial^2 f_i}{\partial y^2} \geq 0 \ \ & \partial^2 f_i}{\partial x^2} \partial^2 f_i}{\partial y^2} - \partial^2 f_i}{\partial x \partial y} \partial^2 f_i}{\partial y \partial x} = 0 \end{aligned} \right.
均大于等于零。因此 Hessian 矩阵为半正定矩阵,即 f_i(x, y) 是凸函数。
因此 f = \sum\limits_{i=0}^{n-1} f_i 也是凸函数。