【困难】安装栅栏

题目描述

在一个二维的花园中,有一些用 (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]);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷冰殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值