[LeetCode] Erect the Fence 豎立柵欄


 

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:

  1. All trees should be enclosed together. You cannot cut the rope to enclose trees that will separate them in more than one group.
  2. All input integers will range from 0 to 100.
  3. The garden has at least one tree.
  4. All coordinates are distinct.
  5. Input points have NO order. No order required for output.

 

這道題給了我們一些樹,每個樹都有其特定的坐標,讓我們用最少的柵欄將其全部包住,讓我們找出在柵欄邊上的樹。其實這道題是凸包問題,就是平面上給了一堆點,讓我們找出一個多邊形,正好包括了所有的點。凸包問題的算法有很多,常見的有八種,參見wiki上的這個帖子。我們來看一種比較常見的算法,卷包裹Gift wrapping算法,又叫Jarvis march算法。這種算法的核心像一種卷包裹的操作,比如說我們把每個點當成牆上的釘子,然后我們有一個皮筋,我們直接將皮筋撐的老大,然后套在所有釘子上松手,其自動形成的形狀就是要求的凸包,也是凸多邊形。腦海中有沒有產生這個畫面?撐起皮筋的邊緣點就是我們要求的關鍵的結點,形象的圖文講解可以參見這個帖子

我們的目標是找到這些點,做法是先找到一個邊緣點,然后按一個方向轉一圈,找到所有的邊緣點,當再次找到起始的邊緣點時結束循環。起始點的選擇方法是找橫坐標最小的點,即最左邊的點,如果有多個橫坐標相同且最小的點也沒有關系,其中任意一個都可以當作起始點。因為某個點的橫坐標或縱坐標任意一個是最小或最大時,該點一定是邊緣上的點。我們把這個起始點標記為first,其坐標標記為firstIdx,然后我們建立一個當前點遍歷cur,初始化為first,當前點坐標curIdx,初始化為firstIdx。然后我們進行循環,我們目標是找到下一個邊緣點next,初始化其為數組中的第一個點,在例子1中,起始點也是數組中的第一個點,這樣cur和next重合了,不過沒有關系,這是個初始化而已。好,現在兩個點已經確定了,我們還需要一個點,這樣三個點就可以利用叉積來求出向量間的夾角,從而根據正負來判斷是否為邊緣點。第三個點的選擇就從數組中的第二個點開始遍歷,如果遍歷到了cur點,直接跳過。然后此時我們算三個點之間的叉積Cross Product,不太了解叉積的菊苣們可以google一些帖子看一看,簡單的來說,就是比如有三個點A,B和C,那么叉積就是求和向量BA,BC都垂直的一個向量,等於兩個向量的長度乘以夾角的正弦值。在之前那道Convex Polygon中,我們就是根據叉積來判斷是否是凸多邊形,要保持凸多邊形,那么每三個點的叉積要同正或同負。這有什么用呢,別急,一會再說。先來說之前的cur和next重合了的情況,根據叉積的計算方法,只要有兩個點重合,那么叉積就為0。比如當cur和next都是A,points[i]是B時,cross是0,此時我們判斷如果points[i]到cur的距離大於next到cur的距離的話,將next移動到points[i]。為啥要判斷距離呢,我們假設現在有種情況,cur是D,next是E,points[i]是F,此時的cross算出是0,而且FD的距離大於ED的距離,則將next移動到F點,是正確的。但假如此cur是D,next是F,pionts[i]是E,此時cross算出來也是0,但是ED的距離小於FD的距離,所以不用講next移動到E,這也make sense。

好,還有兩種情況也需要移動next,一種是當next點和cur點相同的時候直接移動next到points[i],其實這種情況已經在上面的分析中cover了,所以這個判斷有沒有都一樣,有的話能省幾步距離的計算吧。還有一種情況是cross大於0的時候,要找凸多邊形,cross必須同正負,如果我們設定cross大於0移動next,那么就是逆時針來找邊緣點。當我們算出來了next后,如果不存在三點共線的情況,我們可以直接將next存入結果res中,但是有共線點的話,我們只能遍歷所有的點,再次計算cross,如果為0的話,說明共線,則加入結果res中。在大神的帖子中用的是Set可以自動取出重復,C++版本的應該使用指針的Point,這樣才能讓set的插入函數work,不加指針的話就不能用set了,那只能手動去重復了,寫個去重復的子函數來filter一下吧,參見代碼如下:

 

class Solution {
public:
    vector<Point> outerTrees(vector<Point>& points) {
        vector<Point> res;
        Point first = points[0];
        int firstIdx = 0, n = points.size();
        for (int i = 1; i < n; ++i) {
            if (points[i].x < first.x) {
                first = points[i];
                firstIdx = i;
            }
        }
        res.push_back(first);
        Point cur = first;
        int curIdx = firstIdx;
        while (true) {
            Point next = points[0];
            int nextIdx = 0;
            for (int i = 1; i < n; ++i) {
                if (i == curIdx) continue;
                int cross = crossProduct(cur, points[i], next);
                if (nextIdx == curIdx || cross > 0 || (cross == 0 && dist(points[i], cur) > dist(next, cur))) {
                    next = points[i];
                    nextIdx = i;
                }
            }
            for (int i = 0; i < n; ++i) {
                if (i == curIdx) continue;
                int cross = crossProduct(cur, points[i], next);
                if (cross == 0) {
                    if (check(res, points[i])) res.push_back(points[i]);
                }
            }
            cur = next;
            curIdx = nextIdx;
            if (curIdx == firstIdx) break;
        }
        return res;
    }
    int crossProduct(Point A, Point B, Point C) {
        int BAx = A.x - B.x;
        int BAy = A.y - B.y;
        int BCx = C.x - B.x;
        int BCy = C.y - B.y;
        return BAx * BCy - BAy * BCx;
    }
    int dist(Point A, Point B) {
        return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y);
    }
    bool check(vector<Point>& res, Point p) {
        for (Point r : res) {
            if (r.x == p.x && r.y == p.y) return false;
        }
        return true;
    }
};

 

類似題目:

Convex Polygon

 

參考資料:

https://en.wikipedia.org/wiki/Convex_hull_algorithms

http://www.cnblogs.com/Booble/archive/2011/02/28/1967179.html

https://discuss.leetcode.com/topic/89340/quickhull-c-solution-29ms

https://discuss.leetcode.com/topic/89745/c-and-python-easy-wiki-solution

https://discuss.leetcode.com/topic/89323/java-solution-convex-hull-algorithm-gift-wrapping-aka-jarvis-march

https://discuss.leetcode.com/topic/89336/java-graham-scan-with-adapted-sorting-to-deal-with-collinear-points

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM