There are some trees, where each tree is represented by (x,y) coordinate in a two-dimensional garden. Your job is 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. Your task is to help find the coordinates of trees which are exactly located on the fence perimeter.
Example 1:
Input: [[1,1],[2,2],[2,0],[2,4],[3,3],[4,2]]
Output: [[1,1],[2,0],[4,2],[3,3],[2,4]]
Explanation:
Example 2:
Input: [[1,2],[2,2],[4,2]]
Output: [[1,2],[2,2],[4,2]]
Explanation:
Even you only have trees in a line, you need to use rope to enclose them.
Note:
All trees should be enclosed together. You cannot cut the rope to enclose trees that will separate them in more than one group.
All input integers will range from 0 to 100.
The garden has at least one tree.
All coordinates are distinct.
Input points have NO order. No order required for output.
思路:Graham扫描法
首先需要对某个维度进行从小达到排序。这样就能确定其中一个顶点了,我们选择横坐标最小的那个点作为整个坐标的原点。
算法更新过程如上图所示。
步骤:
1、把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,如图中的P0。
2、把所有点的坐标平移一下,使 P0 作为原点,如上图。
3、计算各个点相对于 P0 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前面。例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由几何知识可以知道,结果中第一个点 P1 和最后一个点 P8 一定是凸包上的点。
(以上是准备步骤,以下开始求凸包)
以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的那个点拿出来做当前点,即 P2 。接下来开始找第三个点:
4、连接P0和栈顶的那个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。
5、如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤4。
6、当前点是凸包上的点,把它压入栈,执行步骤7。
7、检查当前的点 P2 是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。
/**
* Definition for a point.
* class Point {
* int x;
* int y;
* Point() { x = 0; y = 0; }
* Point(int a, int b) { x = a; y = b; }
* }
*/
class Solution {
public List<Point> outerTrees(Point[] points) {
return GrahamScan(points);
}
private List<Point> GrahamScan(Point[] points){
int n = points.length;
if (n <= 2) return Arrays.asList(points);
//排序的原因是什么
Arrays.sort(points,new Comparator<Point>(){
@Override
public int compare(Point o1, Point o2) {
return o1.y != o2.y ? o1.y - o2.y : o1.x - o2.x;
}
});
int[] stack = new int[n+2];
int p = 0;
//一个O(n)的循环
for (int i = 0; i < n; i++) {
while (p >= 2 && cross(points[stack[p - 2]], points[i], points[stack[p - 1]]) > 0)
p--;
stack[p++] = i;
}
int inf = p + 1;
for (int i = n -2; i >= 0; i--){
if (equal(points[stack[p-2]], points[i])) continue;
while (p >= inf && cross(points[stack[p-2]], points[i], points[stack[p-1]]) > 0)
p--;
stack[p++] = i;
}
int len = Math.max(p - 1, 1);
List<Point> ret = new ArrayList<>();
for (int i = 0; i < len; i++){
ret.add(points[stack[i]]);
}
return ret;
}
private int cross(Point o, Point a, Point b){
return (a.x-o.x)*(b.y-o.y) - (a.y - o.y) * (b.x - o.x);
}
private boolean equal(Point a, Point b){
return a.x == b.x && a.y == b.y;
}
}