[LintCode]大樓輪廓實現(xiàn)及優(yōu)化

原文發(fā)表在我的博客:大樓輪廓實現(xiàn)及優(yōu)化
求關(guān)注、求交流、求意見缀雳、求建議。

問題

LintCode:大樓輪廓

描述

水平面上有 N 座大樓梢睛,每座大樓都是矩陣的形狀肥印,可以用三個數(shù)字表示 (start, end, height) ,分別代表其在 x 軸上的起點扬绪,終點和高度竖独。大樓之間從遠處看可能會重疊,求出 N 座大樓的外輪廓線挤牛。
外輪廓線的表示方法為若干三元組莹痢,每個三元組包含三個數(shù)字 (start, end, height) ,代表這段輪廓的起始位置墓赴,終止位置和高度竞膳。

樣例

給出三座大樓:

[
 [1, 3, 3],
 [2, 4, 4],
 [5, 6, 1]
]

外輪廓線為:

[
 [1, 2, 3],
 [2, 4, 4],
 [5, 6, 1]
]

實現(xiàn)

一開始看到這道題難度是超難我是不相信的,因為看起來很容易找到思路诫硕,然而測試用數(shù)據(jù)卻非常大坦辟,很難不超時完成,這讓我非常感興趣章办,用了很久來做這道題锉走,以下是我做這道題的思路滨彻,由于用語言描述過于復(fù)雜,所以采用動圖的方式來描述繪制過程(手機瀏覽器可能會有些問題挪蹭,建議用電腦看)亭饵,擬定輸入數(shù)據(jù)為:

[
 [7,9,5],
 [1,3,3],
 [2,5,4],
 [12,13,2],
 [4,10,2],
 [11,14,4]
]

無序逐個插入

問題分析

大樓的數(shù)組并不是有序的,所以首先想到了逐個插入梁厉,再根據(jù)包含辜羊、相交、被包含等不同的情況來繪制逐個繪制輪廓词顾,如下圖:

實現(xiàn) - C++

class Solution {
public:
    vector<vector<int> > buildingOutline(vector<vector<int> > &buildings) {
        vector<vector<int>> result;
        vector<int> buffer1;
        int size = buildings.size();
        for(int j = 0; j < size; j++){
            buffer1 = buildings[j];
            if(result.size() == 0){
                result.push_back(buffer1);
                continue;
            }
            if(result.back()[1] < buffer1[0]){
                result.push_back(buffer1);
                continue;
            }
            if(result.front()[0] > buffer1[1]){
                result.insert(result.begin(), buffer1);
                continue;
            }
            for(int i = 0; i < result.size(); i++){
                if(i > 0){
                    if(result[i -1][1] != result[i][0]){
                        vector<int> buffer2;
                        buffer2.push_back(result[i - 1][1]);
                        buffer2.push_back(result[i][0]);
                        buffer2.push_back(buffer1[2]);
                        result.insert(result.begin() + i, buffer2);
                        continue;
                    }
                }
                if(buffer1[2] > result[i][2]){
                    if(result[i][1] < buffer1[0]){
                        continue;
                    }
                    if(result[i][0] > buffer1[1]){
                        continue;
                    }
                    if(buffer1[0] <= result[i][0] 
                        && buffer1[1] >= result[i][1]){
                        result[i][2] = buffer1[2];
                    }else if(buffer1[0] > result[i][0] 
                        && buffer1[1] >= result[i][1]){
                        vector<int> buffer2;
                        buffer2.push_back(buffer1[0]);
                        buffer2.push_back(result[i][1]);
                        buffer2.push_back(buffer1[2]);
                        result[i][1] = buffer1[0];
                        result.insert(result.begin() + i + 1, buffer2);
                        i++;
                    }else if(buffer1[0] <= result[i][0] 
                        && buffer1[1] < result[i][1]){
                        vector<int> buffer2;
                        buffer2.push_back(result[i][0]);
                        buffer2.push_back(buffer1[1]);
                        buffer2.push_back(buffer1[2]);
                        result[i][0] = buffer1[1];
                        result.insert(result.begin() + i, buffer2);
                        break;
                    }else if(buffer1[0] > result[i][0] 
                        && buffer1[1] < result[i][1]){
                        vector<int> buffer2;
                        buffer2.push_back(result[i][0]);
                        buffer2.push_back(buffer1[0]);
                        buffer2.push_back(result[i][2]);
                        result[i][0] = buffer1[1];
                        result.insert(result.begin() + i, buffer1);
                        result.insert(result.begin() + i, buffer2);
                        break;
                    }
                }
            }
            if(result.back()[1] < buffer1[1]){
                vector<int> buffer2;
                buffer2.push_back(result.back()[1]);
                buffer2.push_back(buffer1[1]);
                buffer2.push_back(buffer1[2]);
                result.push_back(buffer2);
            }
            if(result.front()[0] > buffer1[0]){
                vector<int> buffer2;
                buffer2.push_back(buffer1[0]);
                buffer2.push_back(result.front()[0]);
                buffer2.push_back(buffer1[2]);
                result.insert(result.begin(), buffer2);
            }
        }
        for(int i = 0; i < result.size(); i++){
            if(result[i][0] == result[i][1]){
                result.erase(result.begin() + i);
                i--;
            }
            if(i != 0){
                if(result[i - 1][2] == result[i][2] 
                    && result[i - 1][1] == result[i][0]){
                    result[i][0] = result[i - 1][0];
                    result.erase(result.begin() + i - 1);
                    i--;
                }
            }
        }
        return result;
    }
};

結(jié)果分析

  • 結(jié)果:非常慢八秃,代碼實際寫起來遠比想象的要復(fù)雜,當新大樓與舊輪廓線相交時肉盹,需要分塊兒遍歷舊輪廓線昔驱,效率非常低,LintCode 測試數(shù)據(jù)跑到 53% 時就超時了上忍。
  • 分析:當大樓無序插入時舍悯,需要考慮情況非常多,不僅代碼復(fù)雜容易混亂且執(zhí)行效率低下睡雇,所以也未進行進一步的改正便直接放棄。直接考慮排序后再進行逐個插入饮醇,以減少需要考慮的情況它抱。

有序逐個插入

問題分析

由于考慮到無序插入時情況過多且遍歷次數(shù)過多,導(dǎo)致時間復(fù)雜度很高朴艰,在數(shù)據(jù)量大時運行效率過低观蓄,所以便打算采用預(yù)排序來減少遇到的情況,具體過程如下圖:

結(jié)果預(yù)測

由于再思考這種方法時想到了更高效的方法祠墅,所以沒有進行代碼實現(xiàn)侮穿。雖然上圖的邏輯看起來比較簡單,但是當大量大樓重疊時毁嗦,需要頻繁的修改舊的輪廓亲茅,相對于第一種方法來說也只是減少了遍歷的次數(shù),但是并不能免于分段遍歷舊輪廓狗准,而且判斷是否與舊輪廓相交克锣、包括、被包括需要做的對比也比較多腔长∠睿可想而知,此方法也并不能通過測試捞附。

無序逐個插入 - 拆分為單位大樓

問題分析

由于采用 (start, end, height) 三元組的形式存儲數(shù)據(jù)巾乳,當無序插入時只能采用遍歷判斷的方式來分情況討論您没,由此導(dǎo)致的邏輯混亂使復(fù)雜度很難降低。便想出了將 三元組([1,4,3]) 轉(zhuǎn)化為若干個連續(xù)的單位大樓([1,3],[2,3],[3,3])(因為每個單位大樓跨度都為一胆绊,所以 end 可以不需要表示了)氨鹏,這樣存儲時就可以將每棟樓的 start 作為下標。此時便只需要對比每個單位大樓的高度來決定是否修改輪廓辑舷,整體過程與第一種方法一致:

實現(xiàn) - C++

class Solution {
public:
    vector<vector<int> > buildingOutline(vector<vector<int> > &buildings) {
        vector<vector<int>> result;
        vector<int> draft;
        int dSize = 0;
        int bSize = buildings.size();
        for(int i = 0; i < bSize; i++){
            //新加大樓超出draft范圍則需要擴容
            int cEnd = buildings[i][1];
            if(dSize < cEnd){
                while(dSize < cEnd){
                    draft.push_back(0);
                    dSize++;
                }
            }
            //更新大樓輪廓
            int cValue = buildings[i][2];
            for(int j = buildings[i][0] - 1; j < cEnd - 1; j++){
                if(draft[j] < cValue){
                    draft[j] = cValue;
                }
            }
        }
        //將單位大樓模式轉(zhuǎn)為三元組格式
        int start = 0;
        int value = 0;
        for(int i = 0; i < dSize; i++){
            if(draft[i] != value){
                if(start < i && value != 0){
                    vector<int> building;
                    building.push_back(start + 1);
                    building.push_back(i + 1);
                    building.push_back(value);
                    result.push_back(building);
                }
                start = i;
                value = draft[i];
            }
        }
        return result;
    }
};

結(jié)果分析

  • 結(jié)果:成功將 LintCode 測試數(shù)據(jù)跑到了 77% 喻犁。
  • 分析:轉(zhuǎn)化思路后,從代碼量上來看何缓,邏輯復(fù)雜程度大大降低肢础,拆分大樓后坐標按 vector 坐標存儲,提高了隨機存取的效率碌廓,但是由于拆分存儲传轰,空間復(fù)雜度大大提高。盡管每一步都是簡單的大小對比谷婆,數(shù)據(jù)量較大時仍然會消耗大量的時間慨蛙,這應(yīng)該也是不能通過 LintCode 測試的根本原因。

有序插入 - 拆分為左右墻

問題分析

這種方法解決問題的基本思想就是將每座大樓用 左右兩面墻表示([1,3],[4,-3]) 替換 三元組表示([1,4,3])纪挎,左墻的高度為正期贫,右墻的高度為負,也可以理解為高度的跳躍异袄,因為是從左到右掃描通砍,所以左墻高度升高,右墻高度降低烤蜕。拆分為墻之后按坐標排序封孙,如果坐標相同則根據(jù)高度反向排序,因為優(yōu)先左墻可以避免同樣高且位置相同的兩面墻先結(jié)束再開始的情況讽营,而且優(yōu)先更高的墻也可以減少先低墻后高墻是否需要劃輪廓的不必要判斷虎忌。然后從左至右逐個墻面進行掃描,如下圖:

實現(xiàn) - C++

#include <set>
//墻的數(shù)據(jù)結(jié)構(gòu)
struct JUMP  
{  
    int index; 
    int height;  
    JUMP(int a, int b) : index(a), height(b){}
    //操作符<的定義橱鹏,用于排序
    bool operator< (const JUMP &j)  const  {
        if(index != j.index){
            return index < j.index;
        }else{
            //相等時由高到底排序
            return height > j.height;
        }
    }
}; 
class Solution {
public:
    vector<vector<int> > buildingOutline(vector<vector<int> > &buildings) {
        //數(shù)量提前取出來膜蠢,避免多次調(diào)用浪費時間
        int numOfBuilding = buildings.size();
        int sizeOfJumps = numOfBuilding * 2;
        //拆分為墻存儲
        vector<JUMP> jumps;
        for(int i = 0; i < numOfBuilding; i++){
            jumps.push_back(JUMP(buildings[i][0], buildings[i][2]));
            jumps.push_back(JUMP(buildings[i][1], - buildings[i][2]));
        }
        //對墻進行排序
        sort(jumps.begin(), jumps.end());
        //繪制輪廓
        vector<vector<int>> result;
        //沒有結(jié)束的大樓存起來,multiset插入時會自動排序
        multiset<int> current;
        int prevJump = 0;
        for(int i = 0; i < sizeOfJumps; i++){
            //沒有沒結(jié)束的大樓莉兰,添加左墻高度狡蝶,設(shè)置線段起點
            if(current.empty()){
                current.insert(jumps[i].height);
                prevJump = jumps[i].index;
                continue;
            }
            //如果是左墻
            if(jumps[i].height > 0){
                //如果高于沒結(jié)束的最高的大樓,且線段長度大于0
                if(jumps[i].height > *current.rbegin() 
                && prevJump < jumps[i].index){
                    //畫線 設(shè)置為起點
                    vector<int> jump;
                    jump.push_back(prevJump);
                    jump.push_back(jumps[i].index);
                    jump.push_back(*(--current.end()));
                    result.push_back(jump);
                    prevJump = jumps[i].index;
                }
                //添加墻
                current.insert(jumps[i].height);
            }else{
                //右墻 而且是存起來最高的大樓并且只有一座 線段長度大于0
                if(jumps[i].height == -*current.rbegin() 
                && current.count(-jumps[i].height) < 2 
                && prevJump < jumps[i].index){
                    //畫線 設(shè)置為起點
                    vector<int> jump;
                    jump.push_back(prevJump);
                    jump.push_back(jumps[i].index);
                    jump.push_back(*current.rbegin());
                    result.push_back(jump);
                    prevJump = jumps[i].index;
                }
                //大樓結(jié)束 刪除存儲的大樓
                current.erase(current.lower_bound(-jumps[i].height));
            }
        }
        return result;
    }
};

結(jié)果分析

  • 結(jié)果:經(jīng)測試 C++ 最快可以 3326ms 通過 LintCode 測試贮勃。

  • 分析:由于判斷的條件少贪惹,并且一次性繪制輪廓不進行修改,效率很高寂嘉,經(jīng)受住了大數(shù)據(jù)量的考驗奏瞬。

  • 細節(jié):

  • 遍歷前取出次數(shù)枫绅,避免重復(fù)執(zhí)行影響遍歷效率

  • 數(shù)據(jù)結(jié)構(gòu)設(shè)計有缺陷,左右通過正負判斷降低了代碼的簡潔程度

  • 這道題數(shù)據(jù)結(jié)構(gòu)的選擇很重要硼端,要說的比較多并淋,后面使用單獨一節(jié)來說明。

數(shù)據(jù)結(jié)構(gòu)的選擇

最終作為選擇的容器分別為 vectormultiset珍昨。

  • vector:由于 vector 屬于順序容器县耽,內(nèi)部由 數(shù)組 實現(xiàn),隨機存取效率高镣典,尾部插入刪除效率高兔毙,中間插入刪除效率低。
  • multiset:與 set 唯一的不同就是可以添加等值元素兄春,都屬于關(guān)聯(lián)容器澎剥,內(nèi)部由 紅黑樹 實現(xiàn),插入時會自動排序赶舆,檢索效率高哑姚。

同樣是排序,墻的排序由于不需要插入一次取一次芜茵,所以選擇了 vector 叙量,std::sort()vector 的排序是優(yōu)化過的快排,效率高于 紅黑樹 的堆排序九串。
最開始是存數(shù)組然后自己寫快排排序宛乃,然后發(fā)現(xiàn) std::sort() 的排序比手寫的要快,所以采用了 vector 搭配 std::sort() 進行墻的存儲排序蒸辆。
相對于墻的存儲,未結(jié)束墻存儲使用的 multiset析既,由于每次插入后都要取一次最大值躬贡,multiset 的堆排序作為插入排序的一種,每次插入一個元素都是有序的眼坏,相對于快排的一次成型拂玻,更適合存儲未結(jié)束墻的情況。
起先是打算用 數(shù)組存儲 + 直接插排 的方式宰译,簡直是低估了這道題的難度檐蚜,最終不得不選用 multiset

總結(jié)

因為寫兩個語言的代碼實在是太浪費時間沿侈,所以實現(xiàn)就只使用 C++ 了闯第,主要還是看思路。
通過對這道題的執(zhí)著缀拭,學(xué)到了不少的東西咳短,最重要的是做題的心態(tài)填帽,這里要感謝 @Cicy_Lee 在我不知道難度的情況提問我這道題,如果當時看到這道題難度是超難的話咙好,可能都不會去感興趣甚至打開篡腌,何況是做出來。所以遇到問題還是不要停留在思考一下的程度上勾效,盡量去做一下嘹悼,因為做起來可能遠比想的復(fù)雜。
另外层宫,描述很多東西總是免不了用動畫來表示杨伙, 但是 After Effect 畫動圖效率忒低了,卻又找不到很好的圖形化 HTML5 動畫模塊的制作工具(是模塊不是網(wǎng)頁)卒密,如果誰有很好的工具缀台,求推薦一個。如果實在沒有的話哮奇,有人有興趣一起開發(fā)一個的話請留言告訴我膛腐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鼎俘,隨后出現(xiàn)的幾起案子哲身,更是在濱河造成了極大的恐慌,老刑警劉巖贸伐,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勘天,死亡現(xiàn)場離奇詭異,居然都是意外死亡捉邢,警方通過查閱死者的電腦和手機脯丝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伏伐,“玉大人宠进,你說我怎么就攤上這事∶牯幔” “怎么了材蹬?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吝镣。 經(jīng)常有香客問我堤器,道長,這世上最難降的妖魔是什么末贾? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任闸溃,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘圈暗。我一直安慰自己掂为,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布员串。 她就那樣靜靜地躺著勇哗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寸齐。 梳的紋絲不亂的頭發(fā)上欲诺,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音渺鹦,去河邊找鬼扰法。 笑死,一個胖子當著我的面吹牛毅厚,可吹牛的內(nèi)容都是我干的塞颁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼吸耿,長吁一口氣:“原來是場噩夢啊……” “哼祠锣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咽安,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤伴网,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妆棒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澡腾,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年糕珊,在試婚紗的時候發(fā)現(xiàn)自己被綠了动分。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡红选,死狀恐怖澜公,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纠脾,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布蜕青,位于F島的核電站苟蹈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏右核。R本人自食惡果不足惜慧脱,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贺喝。 院中可真熱鬧菱鸥,春花似錦宗兼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹊漠,卻和暖如春主到,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躯概。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工登钥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娶靡。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓牧牢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姿锭。 傳聞我的和親對象是個殘疾皇子塔鳍,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 標簽(空格分隔): STL 運用STL,可以充分利用該庫的設(shè)計艾凯,讓我為簡單而直接的問題設(shè)計出簡單而直接的解決方案献幔,...
    認真學(xué)計算機閱讀 1,470評論 0 10
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法趾诗,內(nèi)部類的語法蜡感,繼承相關(guān)的語法,異常的語法恃泪,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • 教你如何迅速秒殺掉:99%的海量數(shù)據(jù)處理面試題 本文經(jīng)過大量細致的優(yōu)化后郑兴,收錄于我的新書《編程之法》第六章中,新書...
    Helen_Cat閱讀 7,403評論 1 39
  • (一)Java部分 1贝乎、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,071評論 0 62
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗情连。 張土汪:刷leetcod...
    土汪閱讀 12,724評論 0 33