力扣——587. 安装栅栏(多种Java做法、JavaScript、C、python3做法)

力扣——587. 安装栅栏(多种Java做法、JavaScript、C、python3做法),第1张

  1. 安装栅栏
    在一个二维的花园中,有一些用 (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]]


Java代码:

class Solution {
    // 要先了解 a x b 叉积的定义 这个和我们高中学的 向量积 是一个意思
    // 我们主要是通过 叉积 来求 三叶姐所说的 以 ab 这条边开始 
    //判断到c点 ac是在ab的顺时针还是逆时针方向

    // subtraction(b,a) 求的是 向量 ab  subtraction(c,a) 求的是 向量 ac
    int[] subtraction(int[] a , int[] b){
        return new int[]{a[0] - b[0] , a[1] - b[1]};
    }

    // 叉乘计算一般是空间中的 xyz 但是 在平面2d 中我们只需要把每个三维坐标中压成平面 使得 z 变为 0
    // 那么叉乘公式定义获得到的值 k 就是 k =a.x*b.y-b.x*a.y,我们可以通过这个k值得到很多有用的性质
    // 就比如这里的 如果k>0时,那么a正旋转到b的角度为<180°,如果k<0,那么a正旋转到b的角度为>180°,如果k=0 那么a,b向量平行。
    double cross(int[] a ,int b[]){
        return a[0] * b[1] - a[1] * b[0];
    }

    // 通过平面两点向量叉乘 判断当前 ac 向量是在顺时针方向还是逆时针方向
    double getArea(int[] a, int[] b ,int[] c){
        return cross(subtraction(b,a),subtraction(c,b));
    }
    
    public int[][] outerTrees(int[][] trees) {
        // 先按照 x 排序 再按照 y 排序
        Arrays.sort(trees,(a,b)->{
            return a[0] == b[0] ? a[1] - b[1] : a[0]-b[0];
        });

        int n = trees.length;
        // 三叶姐姐中的 tp 实际上是用于记录 栈顶位置 也是用来记录当前栈顶元素下一位的下标位置
        int top = 0 ;
        // 开了一个 n + 10 的 stack 用于记录
        int[] stk = new int[n+10];
        // 用于记录使用过的点
        boolean[]vis = new boolean[n+10];
        // 起点不进行标记 及此时 stk[1] = 0; stk[0] 不进行标记
        stk[++top] = 0;
        
        for (int i = 1;i<n;i++){
            // 判断当前这个 c 点哈 ,三叶姐姐文中的那个 c点
            int[] c = trees[i];
            // 如果此时栈中的元素大于两个的时候 先说一下 三叶姐姐画的蓝色分割线下面代表的部分 
            while (top >= 2){
                // 这个是 栈顶元素b的下一个元素a  也就是三叶姐姐文中提到的  `栈顶元素的下一位为 a`
                int[] a = trees[stk[top-1]];
                // 栈顶元素 b
                int[] b = trees[stk[top]];
                // 如果此时 ab 向量 与 bc 向量叉积小于0 说明 ab正旋转到bc的角度为>180° 
                // 说白了就是ac在ab的顺时针方向上 ab 在 bc 的逆时针方向上
                // 因为 在下面部分当中 ac 如果在ab的顺时针方向 则说明ac 所囊括的数据一定比 ab多
                // 所以我们需要删除 ab 保留 ac 作为外边界 
                // 此时stk[top]是b的下标 
                // vis[stk[top--]] 代表的是 删除 ab 这条线后 b 点就当做未被访问过得点了
                if (getArea(a,b,c) < 0)vis[stk[top--]] = false;
                // 反之在其逆时针方向上 直接将这个 c点加入其中 跳出当前循环即可
                else break;
            }
            // 加入当前这个点在栈中位置
            stk[++top] = i;
            // 标记当前 c 点被访问过了
            vis[i] = true;
        }
        // 栈中元素个数 说明上部分保留的点的个数
        int size = top;
        // 从右边往左边遍历 说明这是寻找分割线划线后的上面部分也就是说明第二部分
        for (int i = n-1;i>=0;i--){
            // 当该点已经被访问过了,就直接判断下个点去了
            if (vis[i]) continue;
            int [] c = trees[i];
            // 三叶姐姐说的 逻辑条件变更为 `需要将上述「栈顶元素不少于 2 个」的逻辑替换为「栈顶元素大于 m 个」`
            while (top > size){
                // 老样子 从栈中取点
                int[] a = trees[stk[top-1]];
                int[] b = trees[stk[top]];
                // 老样子不解释啦
                if (getArea(a,b,c) < 0){
                    // top--;
                    vis[stk[top--]] = false;
                }else break;
            }
            // 将该点入栈标记这条边是当前符合条件的
            stk[++top] = i;
            // 老样子标记该点已经被访问过了 其实可以省略 因为后面不会再用到该点了
            vis[i] = true;
        }
        // 栈中所记录的下标就是 符合条件的所有点所围成的凸包的最小周长
        int [][] res = new int[top-1][2];
        // 从1开始因为 0 之前没有记录哦~
        for (int i = 1; i<top;i++){
            res[i-1] = trees[stk[i]];
        }
        // 返回结果
        return res;
    }
}


Java代码2:

class Solution {
    public int[][] outerTrees(int[][] trees) {
        int n = trees.length;
        if (n < 4) {
            return trees;
        }
        /* 按照 x 大小进行排序,如果 x 相同,则按照 y 的大小进行排序 */
        Arrays.sort(trees, (a, b) -> {
            if (a[0] == b[0]) {
                return a[1] - b[1];
            }
            return a[0] - b[0];
        });
        List<Integer> hull = new ArrayList<Integer>();
        boolean[] used = new boolean[n];
        /* hull[0] 需要入栈两次,不进行标记 */
        hull.add(0);
        /* 求出凸包的下半部分 */
        for (int i = 1; i < n; i++) {
            while (hull.size() > 1 && cross(trees[hull.get(hull.size() - 2)], trees[hull.get(hull.size() - 1)], trees[i]) < 0) {
                used[hull.get(hull.size() - 1)] = false;
                hull.remove(hull.size() - 1);
            }
            used[i] = true;
            hull.add(i);
        }
        int m = hull.size();
        /* 求出凸包的上半部分 */
        for (int i = n - 2; i >= 0; i--) {
            if (!used[i]) {
                while (hull.size() > m && cross(trees[hull.get(hull.size() - 2)], trees[hull.get(hull.size() - 1)], trees[i]) < 0) {
                    used[hull.get(hull.size() - 1)] = false;
                    hull.remove(hull.size() - 1);
                }
                used[i] = true;
                hull.add(i);
            }
        }
        /* hull[0] 同时参与凸包的上半部分检测,因此需去掉重复的 hull[0] */
        hull.remove(hull.size() - 1);
        int size = hull.size();
        int[][] res = new int[size][2];
        for (int i = 0; i < size; i++) {
            res[i] = trees[hull.get(i)];
        }
        return res;
    }

    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]);
    }
}


JavaScript代码:

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]);
};



C代码:

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */

struct node {
    int x;
    int y;
    int used;
};

int findTop(struct node *now, struct node *peer, struct node *nodes, int cnt, int right) {
    int pos = -1;
    float k;
    float maxk;
    //分割线的k和b (直线方程kx + b)
    float basek = (peer->y - now->y) * 1.0 / (peer->x - now->x);
    float baseb = now->y - basek * now->x;
    for (int i = 0; i < cnt; i++) {
        //已使用或垂直,不可取
        if (nodes[i].used || now->x == nodes[i].x) {
            continue;
        }
        //计算斜率
        k = (nodes[i].y - now->y) * 1.0 / (nodes[i].x - now->x);
        // 右移,在基准线以下
        if (right && (basek * nodes[i].x + baseb) > nodes[i].y * 1.0) {
            continue;
        }
        //左移,在基准线以上
        if (!right && (basek * nodes[i].x + baseb) < nodes[i].y * 1.0) {
            continue;
        }
        //取最大斜率
        if (pos == -1 || maxk < k) {
            pos = i;
            maxk = k;
        }
        //最大斜率相同时,可能同一线上多个点
        if (maxk == k) {
            if (right && nodes[pos].x < nodes[i].x) {
                //往右,取最大的
                pos = i;
            } else if(!right && nodes[pos].x > nodes[i].x) {
                //往左,取最小的
                pos = i;
            }
        }
    }

    //标识最大斜率上的点
    for (int i = 0; i < cnt && pos != -1; i++) {
        if (nodes[i].used) {
            continue;
        }
        k = (nodes[i].y - now->y) * 1.0 / (nodes[i].x - now->x);
        if (maxk == k) {
            nodes[i].used = 1;
        }
    }
    return pos;
}

int** outerTrees(int** points, int pointsSize, int* pointsColSize, int* returnSize, int** returnColumnSizes){
    struct node *nodes = NULL;
    // 左边界上下点
    int leftTop = -1;
    int leftBot = -1;
    //右边界上下点
    int rightBot = -1;
    int rightTop = -1;
    int *outColSize = NULL;
    int **out = 0;
    //左右边界横坐标值
    int minx;
    int maxx;
    int cnt = 0;
    if (points == NULL || *points == NULL || pointsColSize == NULL || returnSize == NULL || returnColumnSizes == NULL) {
        return NULL;
    }
    nodes = (struct node *)malloc(sizeof(struct node) * pointsSize);
    if (nodes == NULL) {
        return NULL;
    }
    memset(nodes, 0, sizeof(struct node) * pointsSize);
    minx = points[0][0];
    maxx = minx;

    // 查找左右边界横坐标
    for (int i = 0; i < pointsSize; i++) {
        nodes[i].x = points[i][0];
        nodes[i].y = points[i][1];
        if (nodes[i].x < minx) {
            minx = nodes[i].x;
        } else if(nodes[i].x > maxx) {
            maxx = nodes[i].x;
        }
    }
    //查找位于左右边界上的极点
    for (int i = 0; i < pointsSize; i++) {
        if (minx == points[i][0]) {
            nodes[i].used = 1;
            if (leftTop == -1 || nodes[i].y > nodes[leftTop].y){
                leftTop = i;
            }
            if (leftBot == -1 || nodes[i].y < nodes[leftBot].y){
                leftBot = i;
            }
        }
        if (maxx == points[i][0]) {
            nodes[i].used = 1;
            if (rightBot == -1 || nodes[i].y < nodes[rightBot].y){
                rightBot = i;
            }
            if (rightTop == -1 || nodes[i].y > nodes[rightTop].y){
                rightTop = i;
            }
        }
    }
    //如果横坐标相同,必定一条线上,全输出
    if (minx == maxx) {
        for (int i = 0; i < pointsSize; i++) {
            nodes[i].used = 1;
        }
    } else {
        //循环处理
        for (int i = 0; i < pointsSize && leftTop != -1; i++) {
            //左上和右上极点连线后,从左上极点开始找位于线上方且最大斜率的点
            leftTop = findTop(nodes + leftTop, nodes + rightTop, nodes, pointsSize, 1);
        }
        
        for (int i = 0; i < pointsSize && rightBot != -1; i++) {
            //右下和左上极点连线后,从右下极点开始找位于线下方且最大斜率的点
            rightBot = findTop(nodes + rightBot, nodes + leftBot, nodes, pointsSize, 0);
        }
    }
    for (int i = 0; i < pointsSize; i++) {
        if (nodes[i].used) {
            cnt++;
        }
    }

    *returnSize = cnt;
    out = malloc(sizeof(int *) * cnt);
    memset(out, 0, sizeof(int *) * cnt);
    outColSize = malloc(sizeof(int) * cnt);
    memset(outColSize, 0, sizeof(int) * cnt);
    for(int i = 0; i < cnt; i++) {
        outColSize[i] = 2;
    }
    *returnColumnSizes = outColSize;
    
    for (int i = 0, j = 0; i < pointsSize; i++) {
        if (!nodes[i].used) {
            continue;
        }
        out[j] = malloc(sizeof(int) * 2);
        out[j][0] = nodes[i].x;
        out[j][1] = nodes[i].y;
        j++;
    }

    free(nodes);
    return out;
}



python3代码:

class Solution:
    def outerTrees(self, points: List[List[int]]) -> List[List[int]]:
        def oriented(p, q, r):
            return (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1])

        if len(points) < 4:
            return points

        points.sort()  # 按照x值正序排列
        hull = [points[0], points[1]]
        for i in range(2, len(points)):
            r = points[i]
            while len(hull) >= 2 and oriented(hull[-2], r, hull[-1]) < 0:
                hull.pop()
            hull.append(r)
        m = set()
        for p in hull:  # 将已经添加的树储存到集合中
            m.add(tuple(p))
        points = points[::-1]  # 按照x值逆序排列
        for i in range(1, len(points)):
            r = points[i]
            while len(hull) >= 2 and oriented(hull[-2], r, hull[-1]) < 0:
                hull.pop()
            if tuple(r) not in m:
                hull.append(r)
                m.add(tuple(r))
        return hull


作者:KJ.JK
本文仅用于交流学习,未经作者允许,禁止转载,更勿做其他用途,违者必究。
文章对你有所帮助的话,欢迎给个赞或者 star,你的支持是对作者最大的鼓励,不足之处可以在评论区多多指正,交流学习

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/733538.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-04-27
下一篇2022-04-27

发表评论

登录后才能评论

评论列表(0条)

    保存