力扣每日一题0423-587. 安装栅栏

123 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

在一个二维的花园中,有一些用 (x, y) 坐标表示的树。由于安装费用十分昂贵,你的任务是先用最短的绳子围起所有的树。只有当所有的树都被绳子包围时,花园才能围好栅栏。你需要找到正好位于栅栏边界上的树的坐标。

示例 1:

输入: [[1,1],[2,2],[2,0],[2,4],[3,3],[4,2]]
输出: [[1,1],[2,0],[4,2],[3,3],[2,4]]
解释:

erect_the_fence_1.png

示例 2:

输入: [[1,2],[2,2],[4,2]]
输出: [[1,2],[2,2],[4,2]]
解释: 
即使树都在一条直线上,你也需要先用绳子包围它们。

erect_the_fence_2.png

意:

  • 所有的树应当被围在一起。你不能剪断绳子来包围树或者把树分成一组以上。
  • 输入的整数在 0 到 100 之间。
  • 花园至少有一棵树。
  • 所有树的坐标都是不同的。
  • 输入的点没有顺序。输出顺序也没有要求。

Jarvis 算法

此题为经典的求凸包的算法,详细的算法原理可以参考「凸包」。

Jarvis\texttt{Jarvis} 算法背后的想法非常简单。首先必须要从凸包上的某一点开始,比如从给定点集中最左边的点开始,例如最左的一点 A1A_{1}。然后选择 A2A_{2} 点使得所有点都在向量 2A1A22⃗\vec{A_{1}A_{2}} 的左方或者右方,我们每次选择左方,需要比较所有点以 A1A_{1} 为原点的极坐标角度。然后以 A2A_{2} 为原点,重复这个步骤,依次找到 A3,A4,,AkA_{3},A_{4},\ldots,A_{k}。 给定原点 pp,如何找到点 qq,满足其余的点 rr 均在向量 pq\vec{pq} 的左边,我们使用「向量叉积」来进行判别。我们可以知道两个向量 pq,qr\vec{pq},\vec{qr} 的叉积大于 00 时,则两个向量之间的夹角小于 180°180 \degree,两个向量之间构成的旋转方向为逆时针,此时可以知道 rr 一定在 pq\vec{pq} 的左边;叉积等于 00 时,则表示两个向量之间平行,p,q,rp,q,r 在同一条直线上;叉积小于 00 时,则表示两个向量之间的夹角大于 180°180 \degree,两个向量之间构成的旋转方向为顺时针,此时可以知道 rr 一定在 pq\vec{pq} 的右边。为了找到点 qq,我们使用函数 cross()\texttt{cross}() ,这个函数有 33 个参数,分别是当前凸包上的点 pp,下一个会加到凸包里的点 qq,其他点空间内的任何一个点 rr,通过计算向量 pq,qr\vec{pq},\vec{qr} 的叉积来判断旋转方向,如果剩余所有的点 rr 均满足在向量 pq\vec{pq} 的左边,则此时我们将 qq 加入凸包中。下图说明了这样的关系,点 rrr 在向量 pq\vec{pq} 的左边。

587_1.png

var outerTrees = function(trees) {
    const n = trees.length;
    if (n < 4) {
        return trees;
    }
    let leftMost = 0;
    for (let i = 0; i < n; i++) {
        if (trees[i][0] < trees[leftMost][0]) {
            leftMost = i;
        }
    }

    const res = [];
    const visit = new Array(n).fill(0);
    let p = leftMost;
    do {
        let q = (p + 1) % n;
        for (let r = 0; r < n; r++) {
            /* 如果 r 在 pq 的右侧,则 q = r */ 
            if (cross(trees[p], trees[q], trees[r]) < 0) {
                q = r;
            }
        }
        /* 是否存在点 i, 使得 p 、q 、i 在同一条直线上 */
        for (let i = 0; i < n; i++) {
            if (visit[i] || i === p || i === q) {
                continue;
            }
            if (cross(trees[p], trees[q], trees[i]) === 0) {
                res.push(trees[i]);
                visit[i] = true;
            }
        }
        if  (!visit[q]) {
            res.push(trees[q]);
            visit[q] = true;
        }
        p = q;
    } while (p !== leftMost);
    return res;
}

const cross = (p, q, r) => {
    return (q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]);
};