Back to all solutions

#1924 - Erect the Fence II

Problem Description

You are given a 2D integer array trees where trees[i] = [xi, yi] represents the location of the ith tree in the garden.

You are asked to fence the entire garden using the minimum length of rope possible. The garden is well-fenced only if all the trees are enclosed and the rope used forms a perfect circle.

A tree is considered enclosed if it is inside or on the border of the circle.

More formally, you must form a circle using the rope with a center (x, y) and radius r where all trees lie inside or on the circle and r is minimum.

Return the center and radius of the circle as a length 3 array [x, y, r]. Answers within 10-5 of the actual answer will be accepted.

Solution

/**
 * @param {number[][]} trees
 * @return {number[]}
 */
var outerTrees = function(trees) {
  const n = trees.length;

  if (n === 1) {
    return [trees[0][0], trees[0][1], 0];
  }

  const shuffled = [...trees];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }

  return getMinimumEnclosingCircle(shuffled, []);

  function distance(p1, p2) {
    return Math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2);
  }

  function getCircleFromTwoPoints(p1, p2) {
    const x = (p1[0] + p2[0]) / 2;
    const y = (p1[1] + p2[1]) / 2;
    const r = distance(p1, p2) / 2;
    return [x, y, r];
  }

  function getCircleFromThreePoints(p1, p2, p3) {
    const [x1, y1] = p1;
    const [x2, y2] = p2;
    const [x3, y3] = p3;

    const d = 2 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));

    if (Math.abs(d) < 1e-10) {
      return null;
    }

    const ux = ((x1 ** 2 + y1 ** 2) * (y2 - y3) + (x2 ** 2 + y2 ** 2) * (y3 - y1)
      + (x3 ** 2 + y3 ** 2) * (y1 - y2)) / d;
    const uy = ((x1 ** 2 + y1 ** 2) * (x3 - x2) + (x2 ** 2 + y2 ** 2) * (x1 - x3)
      + (x3 ** 2 + y3 ** 2) * (x2 - x1)) / d;
    const r = distance([ux, uy], p1);

    return [ux, uy, r];
  }

  function isInsideCircle(point, circle) {
    const [x, y, r] = circle;
    return distance(point, [x, y]) <= r + 1e-7;
  }

  function getMinimumEnclosingCircle(points, boundary) {
    if (boundary.length === 3 || points.length === 0) {
      if (boundary.length === 0) return [0, 0, 0];
      if (boundary.length === 1) return [boundary[0][0], boundary[0][1], 0];
      if (boundary.length === 2) return getCircleFromTwoPoints(boundary[0], boundary[1]);
      return getCircleFromThreePoints(boundary[0], boundary[1], boundary[2]);
    }

    const point = points[0];
    const circle = getMinimumEnclosingCircle(points.slice(1), boundary);
    if (isInsideCircle(point, circle)) {
      return circle;
    }

    return getMinimumEnclosingCircle(points.slice(1), boundary.concat([point]));
  }
};