解題報告:LeetCode The Skyline Problem(畫天際線)


題目出處:https://leetcode.com/submissions/detail/47013144/
題意描述:

給定一系列矩形的左邊坐標Li,右邊坐標Ri,和高度Hi(其中Li按照從小到大的順序排列)。代表城市中一座座高樓。求這些矩形代表的高樓行成的天際線。天際線的定義為:在遠處看這些所有的高樓時看到的輪廓。

數據輸入:

[ [L1, R1, H1], [Li, Ri, Hi]...]為元素類型為三維向量的向量,其中Li,Ri,Hi的含義見題意描述。且輸入數據保證: 0 ≤ Li, Ri ≤ INT_MAX0 < Hi ≤ INT_MAX, 且 Ri - Li > 0

結果輸出:

[[x1, y1], [x2, y2]...]為元素類型為pair的向量,其中xi代表天際線中第i個水平線段的端點x坐標值,yi代表該水平線段的高度。值得注意的是,由於城市中地面也行成的天際線也是城市的天際線的一部分,因此輸出的最后一個pair的y值必為0,代表天際線從此以后的y值均為0。

解決思路:

 思路一:

  首先,看到這道題,第一個最最簡單的思路是直接遍歷每個x值,並尋找每個覆蓋x值的區間並求出最大值,可是,很明顯,高達O(m) 的空間復雜度和O(nm)的時間復雜度(m為最大x值,n為區間個數)肯定會跪,於是我們需要換種想法。

思路二:

  既然我們只關心一段一段的線段,於是聯想到可以通過線段樹來實現這道題,對於輸入的線段進行遍歷處理一遍即可。可是,繁瑣的建樹過程對於我們這些還有無數個大作業小作業的碼農來說簡直不能忍(其實真實原因只是只知線段樹其名,從來沒有親自實現過,不敢貿然入坑),有興趣的同學可以看看附1的代碼實現。當然,線段樹還是比較重要的一種數據結構,算法競賽中都會偶有涉及,對線段樹有興趣的同學可以自行百度或谷歌相關資料。這里只附上noip2012年考過的一道競賽題“借教室”的結題報告(別問我為何記那么清楚,因為我當初就是采用思路一的那種簡單粗暴的方法結果只考了30分)。

思路三:

  既然這兩種方法都跪了,那還有什么好點的方法呢?思路二中將線段看為一個整體的思路值得借鑒,雖然不想自己建樹,但是可以直接利用C++中現有的樹呀。畢竟stl中的map,multimap,set,muiltset可都是一棵棵現成的紅黑樹。

  於是我們就開始了漫長的查文檔的道路,希望從這些模板數據結構中找到可以用到這道題的東西。首先,由於Li,Ri均可能達到INT_MAX的規模,於是我們需要將數據規模為0~INT_MAX的一系列坐標映射到一個合適的表中,即建立一個散列表。multimap就可以實現將線段中端點的x值,y值映射到內部的鍵值中。於是我們只需要將Li,Ri都扔到muitimap中就可以了。可是接下來問題產生了,將Li,Ri都扔到multimap后我們沒法區分那個x值是屬於一個矩形的左邊的值,那個屬於右邊的值。一個比較簡單的想法是在將Li,Ri扔入muitimap時,同時綁定其高度信息,若為Li,則綁定Hi,若為Ri,則綁定-Hi(感謝朋神提供這個完美的思路給我,手動@GDP)。於是,映射的問題解決了,數據規模變成了我們可接受的20000。

  接下來我們需要解決求解每段線段的高度的問題,一種方法是使用一個優先級隊列儲存當前x值下能覆蓋到該點的所有線段的高度。具體做法如下,首先初始化在優先級隊列中插入元素0,接着遍歷每個端點(包括左端點和右端點),若為左端點,則將當前對應的高度Hi加入隊列中,若為右端點,則將其對應的的高度-Hi的絕對值Hi對應的元素從隊列中刪除,添加或刪除后,即可在O(1)的時間內獲取優先級隊列中的最大值height,此最大值即為我們所求的當前點的高度。可是,讓人失望的是,優先級隊列支持了插入操作,不支持任意位置的刪除操作,於是我們需要尋找一種既可以自動排序,又可以快速刪除的一個模板類來代替優先級隊列。顯然multiset是個很好的選擇,於是我們有了如下的代碼(是在不知道multimap應該取什么名字,於是隨意取了個A,囧~):

 1 class Solution {
 2 public:
 3     vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
 4         multimap<int, int> A;
 5         vector<pair<int, int>> ans;
 6         for(int i = 0; i < buildings.size(); i ++)
 7         {
 8             A.emplace(buildings[i][0], buildings[i][2]);
 9             A.emplace(buildings[i][1], -buildings[i][2]);
10         };
11 
12         multiset<int> h;
13         h.insert(0);
14         for (std::multimap<int,int>::iterator it = A.begin(); it!=A.end(); ++it)
15         {
16             if(it->second > 0)
17             {
18                 h.insert(it->second);
19             }
20             else
21             {
22                 std::multiset<int>::iterator it_tmp = h.find(-it->second);
23                 h.erase(it_tmp);
24             }
25             int height = *h.rbegin();
26             ans.push_back(pair<int, int>(it->first, height));
27         }
28         return ans;
29     };
30 };

欣喜的在OJ上提交了代碼后發現,雖然高度的確求對了,但是並沒有進行任何異常處理,比如在輸入[ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ]時,這段代碼將給出[ [2 10],[3 15],[5 15],[7 12],[9 12],[12 0],[15 10],[19 10],[20 8],[24 0] ]的輸出,顯然很多高度相同的地方沒有處理就直接輸出了。

於是我們需要判定,若當前位置的高度和之前的高度相一致,則不需要壓入ans中,於是我們可將第26行代碼做如下修改:

if(ans.size() == 0 || height != ans.back().second)
      ans.push_back(pair<int, int>(it->first, height));

做了這項修改后,之前的輸入的確對了,可是在輸入變為[ [1 3 2],[3, 4, 4] ]時,輸出為[ [1 2],[3 0],[3 3],[3 0] ],顯然也是錯誤的,於是我開始了一些列的特判,一些列的嘗試,比如對第26行做了如下的修改(代碼實在丑的不能看了):

1 if(ans.size() == 0 || height != ans.back().second)
2 {
3     if(ans.size() != 0 && ans.back().first == it->first && ans.back().second < height)
4         ans.pop_back();
5     if(ans.size() == 0 || ((ans.back().first != it->first || ans.back().second < height)
6         &&(height != ans.back().second)))
7         ans.push_back(pair<int, int>(it->first, height));
8 }

最后,不管怎么調整,總是還是有一堆奇怪的bug,而且我的代碼調的越來越丑陋,於是我決定放棄,重新開始審視我的bug,我發現,只要我每次將同一個x所對應的所有Hi都處理完畢再求當前高度就可以完美的解決着一系列bug,於是有了如下的最終版本的代碼:

 1 class Solution {
 2 public:
 3     vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) 
 4     {
 5         multimap<int, int> A;
 6         vector<pair<int, int>> ans;
 7         for(int i = 0; i < buildings.size(); i ++)
 8         {
 9             A.emplace(buildings[i][0], buildings[i][2]);
10             A.emplace(buildings[i][1], -buildings[i][2]);
11         };
12         multiset<int> h;
13         h.insert(0);
14         for (std::multimap<int,int>::iterator it = A.begin(); it!=A.end(); ++it)
15         {
16             int key = it->first;
17             while(it != A.end() && it->first == key)
18             {
19                 if(it->second > 0)
20                     h.insert(it->second);
21                 else
22                     h.erase(h.find(-it->second));
23                 it ++;
24             }
25             it --;
26             int height = *h.rbegin();
27             if(ans.size() == 0 || height != ans.back().second)
28                 ans.push_back(pair<int, int>(it->first, height));
29         }
30         return ans;
31     };
32 };

呼呼,終於結束了,今天一天(哦,昨天好像已經過了額!)都在寫數據結構的作業效率也是夠低的。不過通過這次實驗,為了寫出簡潔高效的代碼,自己真是查了好多好多文檔呀,耐心和英語水平都受到了極大的考驗。

最后,我有個一直沒有想明白的問題,為何對於這道題,從OJ上的提交數據來看,用python,java等寫出來的運行速度比用C,C++寫出來的快得多?是stl寫得不好的原因嗎?C,C++不是一直以速度著稱嗎?

 

 

附1:使用線段樹實現這道題 https://leetcode.com/discuss/65620/share-my-solution-segment-tree-c-888ms

附2:noip2012借教室解題報告 http://hzwer.com/2959.html


免責聲明!

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



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