leetcode 587. Erect the Fence 凸包问题

问题如下:  leetcode 587

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

leetcode 587. Erect the Fence 凸包问题_第1张图片



解法一:暴力解法

我们知道凸包的性质,凸包一定是【最外围】的那些点圈成,所以假设有n个点,那么最多可以构造出n(n1)2条边,算法如下: 
1. 选定一条边,遍历其他n-2个点,如果所有点都在该条边的一侧,则加入凸包集合。 
2. 不断重复步骤1,直到所有边都被遍历过。

如何判断一个点 p3 是在直线 p1p2 的左边还是右边呢?(坐标:p1(x1,y1),p2(x2,y2),p3(x3,y3))我们可以计算: 
alt text 
当上式结果为正时,p3在直线 p1p2 的左侧;当结果为负时,p3在直线 p1p2 的右边。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int calcuTriangle(int x1, int y1, int x2, int y2, int x3, int y3)
{
    return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3;
}

//O(n^3)
set> tubao_baoli(vector>& tree)
{
    set> ret;
    if (tree.size() <= 1)
        return ret;
    
    for (int i = 0; i < tree.size(); i++)
    {
        for (int j = i + 1; j < tree.size(); j++)
        {
            int onesize = 0, othersize = 0;
            for (int k = 0; k < tree.size(); k++)
            {
                if (k == i || k == j)
                    continue;
                int tmp = calcuTriangle(tree[i][0], tree[i][1], tree[j][0], tree[j][1], tree[k][0], tree[k][1]);
                if (tmp > 0)
                    onesize ++;
                else if (tmp < 0)
                    othersize ++;
            }
            if ( onesize == 0 || othersize == 0) //如果某一边没有点,当前两个点都是凸点
            {
                ret.insert(vector {tree[i][0], tree[i][1]});
                ret.insert(vector {tree[j][0], tree[j][1]});
            }
        }
    }
    return ret;
}

int main()
{
    int n = 5;
    vector> trees{{1,1},{2,0},{4,2},{3,3},{2,2},{2,4}};
    set> ret = tubao_baoli(trees);
    for (auto it : ret)
        cout<



解法二:Graham扫描法

leetcode 587. Erect the Fence 凸包问题_第2张图片

第一步:找y最小的点(如果相等就找其中x最小的)作为基准点P0。

第二步:把所有点的坐标平移一下,使 P0 作为原点,如上图。

第三步:计算各个点相对于 P0 的幅角 α (弧度),按从小到大的顺序对各个点排序。

            当 α 相同时,分条件考虑:

                    如果当前弧度是最大弧度,说明在最左边边界,距离 P0 比较远的排在前面。

                    如果当前弧度是最小弧度,说明在最右边边界,距离 P0 比较近的排在前面。          

                    如果在中间,那就都可以。  

            例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由几何知识可以知道,结果中第一个点 P1 和最后一             个点 P8 一定是凸包上的点。 

第四步:我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的所有点拿出来做当前点,遍历进行第五步。

第五步:连接栈顶的第二个点和栈顶的那个点,得到直线 L 。

        看当前点是在直线 L 的右边还是左边。

                    如果在直线的右边就说明现在栈顶的点不是凸点,将栈顶的点弹出。当前点不变。

                    如果在直线上,或者在直线的左边,说明当前栈顶的点是满足条件的,把当前点入栈。遍历后一个点。

第六步:当把步骤3求得的结果里的所有点都遍历完了,还剩在栈里面的点就是凸点。导出即可。


代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

struct Point {
    int x;
    int y;
    Point() : x(0), y(0) {}
    Point(int a, int b) : x(a), y(b) {}
};

class MYPoint{
public:
    MYPoint(int a, int b, double c):x(a), y(b),hudu(c){}
    int x;
    int y;
    double hudu;
    static double maxHuDu;
    bool operator<(const MYPoint& b) const
    {
        if (this->hudu == b.hudu)
        {
            if (this->hudu == maxHuDu)  //说明是左侧的边界。从远到近
                return (pow(this->x, 2) + pow(this->y, 2)) > (pow(b.x, 2) + pow(b.y, 2));
            else                        //如果是右侧边界,从近到远。如果不是边界,都可以(也使用从近到远)
                return (pow(this->x, 2) + pow(this->y, 2)) < (pow(b.x, 2) + pow(b.y, 2));
        }
        return this->hudu < b.hudu;
    }
};

double MYPoint::maxHuDu = INT_MIN;

class Solution {
public:
    vector outerTrees(vector& points)
    {
        if (points.size() <= 3)
            return points;
        
        //首先找P0点(y最小的点)
        int minY = INT_MAX, minYpos;
        for (int i = 0; i < points.size(); i++)
        {
            if (points[i].y < minY)
            {
                minY = points[i].y;
                minYpos = i;
            }
            else if (points[i].y == minY && points[i].x < points[minYpos].x)
            {
                minY = points[i].y;
                minYpos = i;
            }
        }
        
        //cout<<"标准点:"< ret; //返回的凸点
        
        vector mypoints;
        for (int i = 0; i < points.size(); i++)
        {
            if (i == minYpos)
                continue;
            
            double yy = points[i].y - points[minYpos].y, xx = points[i].x - points[minYpos].x;
            double hudu = atan2(yy, xx);
            MYPoint::maxHuDu = dou_max(MYPoint::maxHuDu, hudu);   //要把最大弧度存下来。之后排序的一个关键点!
            mypoints.push_back(MYPoint(points[i].x - points[minYpos].x, points[i].y - points[minYpos].y, hudu)); //存的是相对位置。最后要补偿回来。
        }
        sort(mypoints.begin(), mypoints.end());
        
//        int jishu = 1;
//        for (auto it : mypoints)
//            cout<<"P"< stk;
        stk.push_back(MYPoint(0, 0, 0)); //压入P0(坐标[0,0])
        stk.push_back(mypoints[0]);      //压入P1
        for (int i = 1; i < mypoints.size(); i++)
        {
            //如果在左边或者在线上 就入栈
            if (calcuTriangle(stk[stk.size() - 2].x, stk[stk.size() - 2].y, stk.back().x, stk.back().y, mypoints[i].x, mypoints[i].y))
            {
                stk.push_back(mypoints[i]);
                //cout<<"P"<= 0; //判断是否在左边
    }
    
    double dou_max(double a, double b)
    {
        return (a > b) ? a : b;
    }
};

int main()
{
    //vector> trees{{1,1},{2,0},{4,2},{3,3},{2,2},{2,4}};
    //vector> trees{{3,0},{4,0},{5,0},{6,1},{7,2},{7,3},{7,4},{6,5},{5,5},{4,5},{3,5},{2,5},{1,4},{1,3},{1,2},{2,1},{4,2},{0,3}};
    //vector> trees{{0,0},{0,1},{0,2},{1,2},{2,2},{3,2},{3,1},{3,0},{2,0}};
    //vector> trees{{0,2},{0,4},{0,5},{0,9},{2,1},{2,2},{2,3},{2,5},{3,1},{3,2},{3,6},{3,9},{4,2},{4,5},{5,8},{5,9},{6,3},{7,9},{8,1},{8,2},{8,5},{8,7},{9,0},{9,1},{9,6}};
    vector> trees{{1,0},{0,0},{2,0},{3,0}};
    
    vector points;
    for (int i = 0; i < trees.size(); i++)
        points.push_back(Point(trees[i][0], trees[i][1]));
    
    vector ret = Solution().outerTrees(points);
    
    for (auto it : ret)
        cout<

关于leetcode 587的问题:

上面的代码跑587在 

[[0,0],[0,1],[0,2],[1,2],[2,2],[3,2],[3,1],[3,0],[2,0]] 会报错。

但是单独调试这个实例又是正确的答案。所以很困惑。



你可能感兴趣的:(leetcode)