Leetcode-The Skyline Problem題解

來源:互聯網
上載者:User

標籤:

一. 題目 Leetcode平台上天際線問題 

A city‘s skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Now suppose you are given the locations and height of all the buildings as shown on a cityscape photo (Figure A), write a program to output the skyline formed by these buildings collectively (Figure B).

 

The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi], where Li and Ri are the x coordinates of the left and right edge of the ith building, respectively, and Hi is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX0 < Hi ≤ INT_MAX, and Ri - Li > 0. You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0.

For instance, the dimensions of all buildings in Figure A are recorded as: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ] .

The output is a list of "key points" (red dots in Figure B) in the format of [ [x1,y1], [x2, y2], [x3, y3], ... ] that uniquely defines a skyline. A key point is the left endpoint of a horizontal line segment. Note that the last key point, where the rightmost building ends, is merely used to mark the termination of the skyline, and always has zero height. Also, the ground in between any two adjacent buildings should be considered part of the skyline contour.

For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ].

Notes:

  • The number of buildings in any input list is guaranteed to be in the range [0, 10000].
  • The input list is already sorted in ascending order by the left x position Li.
  • The output list must be sorted by the x position.
  • There must be no consecutive horizontal lines of equal height in the output skyline. For instance, [...[2 3], [4 5], [7 5], [11 5], [12 7]...] is not acceptable; the three lines of height 5 should be merged into one in the final output as such: [...[2 3], [4 5], [12 7], ...]

 

二.解題思路

萌新在剛剛看到這道題的時候表示是無從下手的,後來在看時辰巨巨的部落格時瞭解到了線段樹這個資料結構,感覺這個資料結構真是很牛逼啊,也很適合解決這道題~

首先貼上一個講解線段樹的部落格,也是從時辰的部落格裡看到的,感覺對線段樹的講解的確很生動形象,範例給的也很有啟發性。

線段樹部落格

就像上文部落格中作者提到的:

線段樹的每個節點上往往都增加了一些其他的域。在這些域中儲存了某種動態維護的資訊,視不同情況而定。這些域使得線段樹具有極大的靈活性,可以適應不同的需求。

這段話體現了線段樹靈活性的好處,為了適應這道題的要求,我設計了這樣的線段樹資料結構:

typedef struct SegmentNode{int start;      //線段樹的區間左端點int end;        //線段樹的區間有端點int isFlat;      //當前區間內的天際線是否是水平的int height;      //如果isFlat等於1,代表當前區間天際線的高度,否則無意義SegmentNode* left;  //左兒子SegmentNode* right;  //右兒子}SegmentNode;

  解題思路是這樣的,初始時天際線是一條高度為0的水平線,每添加一個矩形(對應線段樹插入節點的操作)就更新天際線。

  假設已經有了一條天際線,那麼在此基礎上添加一個矩形時,對於線段樹的某個端點,若其所代表的區間包含新添加矩形所在區間,天際線的變化將會有以下幾種可能的情況:

  1. 原有的天際線是水平的

      這時如果矩形高度小於等於原有高度,那麼天際線不會發生變化

      如果矩形高度大於原有高度,如果矩形所在區間恰好等於該線段樹節點所代表的區間,那麼直接更新該節點的height值為矩形高度。如果矩形所在區間小於等於該線段樹節點所在區間,那麼將該節點的isFlat值重設為0,對該端點左右子樹進行遞迴,直至找到恰好等於矩形所在區間的線段樹端點為止,這裡需要注意的一點是如果矩形和左右節點所代表的區間都有重合的部分,那麼需要將矩形分裂為兩個矩形進行操作。

  2. 原有的天際線不是水平的,那麼對該端點左右子樹遞迴,直至找到isFlat=1的端點為止。

以上過程都是在插入線段樹節點過程中進行的,插入節點函數的代碼如下:

void insertNode(int l, int r, int h, SegmentNode* tree)      //因為這個函數是遞迴調用的,所以可以保證矩形範圍在對應節點區間內{if(!tree)return;if(tree->isFlat)                        //當前該區間天際線水平{int mid = tree->start / 2 + tree->end / 2;if((tree->start % 2) && (tree->end % 2))mid++;                    //求該區間的中間節點if(h <= tree->height)                //當前該區間天際線高度大於矩形高度return;if(tree->start == l && tree->end == r)      //當前區間即是矩形區間{tree->height = h;return;}else if(mid >= r)                  //矩形全部都在左子樹代表的區間中{tree->isFlat = 0;if(!tree->left){tree->left = createTree(tree->start, mid);tree->right = createTree(mid, tree->end);tree->right->height = tree->height;tree->left->height = tree->height;}insertNode(l, r, h, tree->left);//insertNode(mid, tree->end, tree->height, tree->right);}else if(mid <= l)                            //矩形全部都在右子樹對應區間中{tree->isFlat = 0;if(!tree->right){tree->left = createTree(tree->start, mid);tree->right = createTree(mid, tree->end);tree->left->height = tree->height;tree->right->height = tree->height;}insertNode(l, r, h, tree->right);}else                                      //矩形橫跨左右子樹區間{tree->isFlat = 0;if(!tree->left){tree->left = createTree(tree->start, mid);tree->right = createTree(mid, tree->end);tree->left->height = tree->height;tree->right->height = tree->height;}insertNode(l, mid, h, tree->left);insertNode(mid, r, h, tree->right);}}else                                          //當前線段樹不平{int mid = tree->start / 2 + tree->end / 2;if((tree->start % 2) && (tree->end % 2))mid++;if(tree->start == l && tree->end == r){insertNode(l, mid, h, tree->left);insertNode(mid, r, h, tree->right);}else if(mid >= r){insertNode(l, r, h, tree->left);}else if(mid <= l){insertNode(l, r, h, tree->right);}else{insertNode(l, mid, h, tree->left);insertNode(mid, r, h, tree->right);}}}

  於是在添加完了所有的矩形之後,對線段樹進行中序遍曆,線段樹的所有葉子節點儲存的就是天際線的資訊了。最後在Leetcode平台上提交的測試代碼是這樣的(因為之前線段樹的代碼部分有注釋了,這裡相應部分就不再寫注釋了):

typedef struct SegmentNode{int start;int end;int isFlat;int height;SegmentNode* left;SegmentNode* right;}SegmentNode;SegmentNode* createTree(int leftRange, int rightRange)          //線段樹的初始化{SegmentNode* tree = new SegmentNode;tree->start = leftRange;tree->end = rightRange;tree->left = NULL;tree->right = NULL;tree->isFlat = 1;tree->height = 0;return tree;}void insertNode(int l, int r, int h, SegmentNode* tree){if(!tree)return;if(tree->isFlat){int mid = tree->start / 2 + tree->end / 2;if((tree->start % 2) && (tree->end % 2))mid++;if(h <= tree->height)return;if(tree->start == l && tree->end == r){tree->height = h;return;}else if(mid >= r){tree->isFlat = 0;if(!tree->left){tree->left = createTree(tree->start, mid);tree->right = createTree(mid, tree->end);tree->right->height = tree->height;tree->left->height = tree->height;}insertNode(l, r, h, tree->left);//insertNode(mid, tree->end, tree->height, tree->right);}else if(mid <= l){tree->isFlat = 0;if(!tree->right){tree->left = createTree(tree->start, mid);tree->right = createTree(mid, tree->end);tree->left->height = tree->height;tree->right->height = tree->height;}insertNode(l, r, h, tree->right);}else{tree->isFlat = 0;if(!tree->left){tree->left = createTree(tree->start, mid);tree->right = createTree(mid, tree->end);tree->left->height = tree->height;tree->right->height = tree->height;}insertNode(l, mid, h, tree->left);insertNode(mid, r, h, tree->right);}}else{int mid = tree->start / 2 + tree->end / 2;if((tree->start % 2) && (tree->end % 2))mid++;if(tree->start == l && tree->end == r){insertNode(l, mid, h, tree->left);insertNode(mid, r, h, tree->right);}else if(mid >= r){insertNode(l, r, h, tree->left);}else if(mid <= l){insertNode(l, r, h, tree->right);}else{insertNode(l, mid, h, tree->left);insertNode(mid, r, h, tree->right);}}}void getResult(SegmentNode* tree, vector<pair<int, int>>& result, int& lastHeight){if(!tree)return;pair<int, int> temp;if(tree->isFlat)                        //如果該節點對應區間是平坦的,得到結果,不然對左右子樹進行遞迴{temp.first = tree->start;temp.second = tree->height;if(temp.second != lastHeight)            //如果該節點高度和上一個節點高度相等,說明天際線不發生彎折,就不輸出這個節點資訊了{result.push_back(temp);}lastHeight = temp.second;return;}else{getResult(tree->left, result, lastHeight);getResult(tree->right, result, lastHeight);}}class Solution {public:vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {int rightRange = 0;for(int i = 0; i < buildings.size(); i++){if(buildings[i][1] > rightRange)rightRange = buildings[i][1];}                                                  //確定線段樹根節點的區間範圍SegmentNode* tree = createTree(0, rightRange);for(int i = 0; i < buildings.size(); i++){insertNode(buildings[i][0], buildings[i][1], buildings[i][2], tree);      //逐個插入矩形,更新天際線}vector<pair<int, int>> result;int lastHeight = 0;int lastPos = 0;getResult(tree, result, lastHeight);                            //得到天際線pair<int, int> temp;temp.first = rightRange;temp.second = 0;if(rightRange)result.push_back(temp);                                //添加天際線的最後一個節點return result;}};

  在提交過程中,出現了一些問題:一開始我在建立線段樹時直接建立了完全二叉樹,結果在測試範例[0, 2147483647, 2147483647]時出現了memory limit exceeded錯誤,後來改成在建立線段樹中只建立根節點,在插入節點過程中再根據需要申請空間,問題得到瞭解決;還有一些各種各樣的錯誤,基本都是因為自己腦殘造成的。。。

最後的提交結果,可以看到運行速度還是挺快的,可能是因為沒有使用STL的結果。

  

三. 總結

解這道題的過程還是很有收穫的,主要體現在對線段樹這個資料結構有了初步的瞭解,調試代碼的過程也讓我對自己可能會犯什麼五花八門的錯誤有了新的認識,感覺姿勢水平得到了提高。最後,如果你看到了我的這篇部落格,希望我們共同學習,共同進步~

Leetcode-The Skyline Problem題解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.