587柵の設置
二次元の庭で、座標(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]]
説明:
実施例2:
入力:[1,2]、[2,2]、[4,2]
出力:[1,2]、[2,2]、[4,2]
説明:
木が直線である場合であっても上の、あなたがロープでそれらを囲む必要があります。
注意:
すべての木が一緒に同封されることになります。あなたは一つのグループよりも多くの木や木にを取り囲むように、文字列をカットすることはできません。
0と100の間の入力整数。
少なくとも一つの木の庭。
すべての木が異なっている座標。
何点入力シーケンスません。出力順序も必要とされていません。
class Solution {
public int[][] outerTrees(int[][] points) {
Set<int[]> hull = new HashSet<>();
// 如果树的棵数小于 4 ,那么直接返回
if (points.length < 4) {
for (int[] p : points) hull.add(p);
return hull.toArray(new int[hull.size()][]);
}
// 找到最左边的点
int leftMost = 0;
for (int i = 0; i < points.length; i++) {
if (points[i][0] < points[leftMost][0]) leftMost = i;
}
int p = leftMost;
do {
int q = (p + 1) % points.length;
for (int i = 0; i < points.length; i++) {
// 如果 i 点在 pq 线下方,则使用 i 点
if (orientation(points[p], points[i], points[q]) < 0) q = i;
}
for (int i = 0; i < points.length; i++) {
// p、q、i 在同一条线上的情况,并且 i 在 p 和 q 的中间的时候
// 也需要将这个点算进来
if (i != p && i != q
&& orientation(points[p], points[i], points[q]) == 0
&& inBetween(points[p], points[i], points[q])) {
hull.add(points[i]);
}
}
hull.add(points[q]);
// 重置 p 为 q,接着下一轮的遍历
p = q;
} while (p != leftMost);
return hull.toArray(new int[hull.size()][]);
}
// 以下 pq 和 qr 都是向量
// pq * qr > 0 表示 r 点在 pq 线上方
// pq * qr < 0 表示 r 点在 pq 线下方
// pq * qr = 0 表示 p、q、r 一条线
// |(q[0]-p[0]) (q[1]-p[1])|
// pq * qr = | | = (q[0]-p[0]) * (r[1]-q[1]) - (r[0]-q[0]) * (q[1]-p[1])
// |(r[0]-q[0]) (r[1]-q[1])|
private int orientation(int[] p, int[] r, int[] q) {
return (q[0] - p[0]) * (r[1] - q[1]) - (r[0] - q[0]) * (q[1] - p[1]);
}
// 判断 r 点是不是在 p 点和 q 点之间,需要考虑以下两种情况:
// 1. q 点在 p 点的左边或者右边
// 2. q 点在 p 点的上边或者下边
private boolean inBetween(int[] p, int[] r, int[] q) {
boolean a = r[0] >= p[0] && r[0] <= q[0] || r[0] <= p[0] && r[0] >= q[0];
boolean b = r[1] >= p[1] && r[1] <= q[1] || r[1] <= p[1] && r[1] >= q[1];
return a && b;
}
}