题目描述
在一个二维的花园中,有一些用 (x, y) 坐标表示的树。由于安装费用十分昂贵,你的任务是先用最短的绳子围起所有的树。只有当所有的树都被绳子包围时,花园才能围好栅栏。你需要找到正好位于栅栏边界上的树的坐标。
解题思路
这是计算几何学中经典的寻找凸包问题,可以使用Graham算法或Jarvis算法进行求解。但需要注意的是,经典的凸包并不包含凸组合(两点连线上的点称为这两点的凸组合),但此题中,需要输出凸组合,所以需要对这种边界问题考虑周全。
Graham算法
class Solution {
public int[][] outerTrees(int[][] trees) {
int n=trees.length;
if (n<=3) return trees;
//选取p0
int[] p0=trees[0];
int k=0;//保存p0的索引
for(int i=1;i<n;i++){
if(trees[i][1]<p0[1]) {
p0=trees[i];
k=i;
}
else if(trees[i][1]==p0[1] && trees[i][0]<p0[0]){
p0=trees[i];
k=i;
}
}
//找到任意一个极角最小的点
int min=(k!=0?0:1);
for(int i=0;i<n;i++){
if(i==k) continue;
if(direction(p0,trees[min],trees[i])>0) min=i;
}
//根据极角排序
int[][] tree=new int[n-1][2];
tree[0]=trees[k!=0?0:1];
for(int i=1;i<n;++i){
if(i==(k!=0?k:1)) continue;
int p=i;
if(i>(k!=0?k:1))p--;
for(;p>0;p--){
int dir=direction(p0,tree[p-1],trees[i]);
if(dir>0) tree[p]=tree[p-1];
else if(dir==0) {
double d=dis(p0, tree[p - 1], trees[i]);
if ((direction(p0,trees[min],trees[i])==0 && d < 0) || (direction(p0,trees[min],trees[i])!=0 && d > 0))
tree[p] = tree[p - 1];
else break;
}
else break;
}
tree[p]=trees[i];
}
//初始化栈
ArrayList<int[]> stack=new ArrayList<>();
stack.add(p0);
stack.add(tree[0]);
stack.add(tree[1]);
//遍历所有顶点,返回凸包
for(int i=2;i<n-1;i++){
while (direction(stack.get(stack.size()-2),stack.get(stack.size()-1),tree[i])>0)
stack.remove(stack.size()-1);
stack.add(tree[i]);
}
int[][] res=new int[stack.size()][2];
for(int i=0;i<stack.size();i++){
res[i]=stack.get(i);
}
return res;
}
public int direction(int[] p0,int[] p1,int[] p2){
return (p2[0]-p0[0])*(p1[1]-p0[1])-(p1[0]-p0[0])*(p2[1]-p0[1]);
}
public double dis(int[] p0,int[] p1,int[] p2){
double d1=Math.sqrt(Math.pow(p0[0]-p1[0],2)+Math.pow(p0[1]-p1[1],2));
double d2=Math.sqrt(Math.pow(p0[0]-p2[0],2)+Math.pow(p0[1]-p2[1],2));
return d2-d1;
}
}
Jarvis算法
class Solution {
public int[][] outerTrees(int[][] trees) {
int n = trees.length;
if (n < 4) {
return trees;
}
int leftMost = 0;
for (int i = 0; i < n; i++) {
if (trees[i][0] < trees[leftMost][0]) {
leftMost = i;
}
}
List<int[]> res = new ArrayList<int[]>();
boolean[] visit = new boolean[n];
int p = leftMost;
do {
int q = (p + 1) % n;
for (int 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 (int i = 0; i < n; i++) {
if (visit[i] || i == p || i == q) {
continue;
}
if (cross(trees[p], trees[q], trees[i]) == 0) {
res.add(trees[i]);
visit[i] = true;
}
}
if (!visit[q]) {
res.add(trees[q]);
visit[q] = true;
}
p = q;
} while (p != leftMost);
return res.toArray(new int[][]{});
}
public int cross(int[] p, int[] q, int[] r) {
return (q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]);
}
}