​LeetCode刷题实战587:安装栅栏

算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !

今天和大家聊的问题叫做 安装栅栏,我们先来看题面:

https://leetcode-cn.com/problems/erect-the-fence/

You are given an array trees where trees[i] = [xi, yi] represents the location of a tree in the garden.

You are asked to fence the entire garden using the minimum length of rope as it is expensive. The garden is well fenced only if all the trees are enclosed.

Return the coordinates of trees that are exactly located on the fence perimeter.

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

示例

解题

https://blog.51cto.com/u_15354358/3756256

思路:凸包模板题。

这个方法的具体实现为:首先选择一个凸包上的初始点 bmbm 。我们选择 y 坐标最小的点为起始点,如果有相同的最小 y 坐标,我们选择 x 坐标最小的,这个点被记为动图中的点 0 。然后我们将给定点集按照相对初始点的极角坐标排序(也就是从点 0 出发的一条直线)。

这一排序过程大致给了我们在逆时针顺序选点时候的思路。为了将点排序,我们使用上一方法使用过的函数 orientation 。极角顺序更小的点排在数组的前面。如果有两个点相对于点 0 在同一方向上,我们将它们按照与点 0 的距离排序。

我们还需要考虑另一种重要的情况,如果共线的点在凸壳的最后一条边上,我们需要从距离初始点最远的点开始考虑起。所以,在将数组排序后,我们从尾开始遍历有序数组并将共线且朝有序数组尾部的点反转顺序,因为这些点是形成凸壳过程中尾部的点,所以在经过了这些处理以后,我们得到了求凸壳时正确的点的顺序。

现在我们从有序数组最开始两个点开始考虑。我们将这条线上的点放入栈中。然后我们从第三个点开始遍历有序数组 pointspoints 。如果当前点与栈顶的点相比前一条线是一个“左拐”或者是同一条线段上,我们都将当前点添加到栈顶,表示这个点暂时被添加到凸壳上。

检查左拐或者右拐使用的还是 orientation 函数。如果函数返回值大于 0 ,这表示当前点与栈顶点与上一条线之间的关系是逆时针的(即右拐的)。类似的,如果函数返回值是负数,表示是左拐。

如果当前点与上一条线之间的关系是右拐的,说明上一个点不应该被包括在凸壳里,因为它在边界的里面(正如动画中点 4 )。所以我们将它从栈中弹出并考虑倒数第二条线的方向。

重复这一过程,弹栈的操作会一直进行,直到我们当前点在凸壳中出现了右拐。这表示这时凸壳中只包括边界上的点而不包括边界以内的点。在所有点被遍历了一遍以后,栈中的点就是构成凸壳的点。

class Solution {
  
  class node {
    
    int x,y; 
    public node(int x,int y) {
      this.x=x;
      this.y=y;
    }
  }
  
  private node st;
  
  class mySort implements Comparator<node>{

    @Override
    public int compare(node o1, node o2) {
      // TODO 自动生成的方法存根
      int diff=Cross(st,o1,o2)-Cross(st,o2,o1);
      if(diff==0)
        return dis(st,o1)-dis(st,o2);
      return diff>0?1:-1;
    }
    
  }
  
    public int[][] outerTrees(int[][] points) {
        
      if(points.length<=3) return points;
      
      node[] q=new node[points.length+1];
      node[] arr=new node[points.length];
      st=new node(Integer.MAX_VALUE,Integer.MAX_VALUE);
      
      for(int i=0;i<points.length;i++) {
        arr[i]=new node(points[i][0],points[i][1]);
        if(arr[i].y<st.y || arr[i].y==st.y && arr[i].x<st.x) {
          st.x=arr[i].x; st.y=arr[i].y;
        }
      }
      
      Arrays.parallelSort(arr,new mySort());
      
      int p=arr.length-1;
      while(p>=0 && Cross(st,arr[arr.length-1],arr[p])==0)
        p--;
      for(int l=p+1,r=arr.length-1;l<r;l++,r--) {
        node tmp=arr[l];
        arr[l]=arr[r];
        arr[r]=tmp;
      }
      
      int top=2;
      q[1]=new node(arr[0].x,arr[0].y);
      q[2]=new node(arr[1].x,arr[1].y);
      
      for(int i=2;i<arr.length;i++) {
        while(top>1 && Cross(q[top-1],q[top],arr[i])>0)
          top--;
        q[++top]=arr[i];
      }
      
      int[][] ans=new int[top][2];
      for(int i=1;i<=top;i++) {
        ans[i-1][0]=q[i].x;
        ans[i-1][1]=q[i].y;
      }
      
      return ans;
    }
    
    //求叉积,若大于0则表明新的点在直线右侧
    private int Cross(node p1,node p2,node p3) {
      return (p2.x-p1.x)*(p3.y-p2.y)-(p3.x-p2.x)*(p2.y-p1.y);
    }
    
    private int dis(node p1,node p2) {
      return (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);
    }
}

好了,今天的文章就到这里,如果觉得有所收获,请顺手点个在看或者转发吧,你们的支持是我最大的动力 。

上期推文:

LeetCode1-580题汇总,希望对你有点帮助!

LeetCode刷题实战581:最短无序连续子数组

LeetCode刷题实战582:杀掉进程

LeetCode刷题实战583:两个字符串的删除操作

LeetCode刷题实战584:寻找用户推荐人

LeetCode刷题实战585:2016年的投资

LeetCode刷题实战586:订单最多的客户

86a50abdc2da6a210657636f0c3e42ce.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值