解题报告:The Skyline Problem(画天际线)
题目出处:https://leetcode.com/problems/the-skyline-problem/
题目描述:

输入三元组[Li, Ri, Hi],代表建筑的左右坐标,以及高度,构成图A。
要求画出天际线,如B图所示,输出为[[x1,y1], [x2, y2], [x3, y3], ... ],一串二元组的集合,分别为轮廓的左端点,x代表横坐标,y代表纵坐标高度。
解题思路:
思路一:
首先,看到这道题,最最简单的思路是直接遍历每个x值,简单粗暴正面刚,将每个覆盖x值的区间合并并求出最大值,从而画出天际线,找到左端点。但这个思路显然要跪,毕竟最先想到的90%都有问题,这个思路的空间复杂度为O(m) ,时间复杂度为O(nm)(m为最大x值,n为区间个数),所以放弃这个思路,继续思考。
思路二:
1、通过简单的分析,我们可以发现,对于天际线起作用的实际上是建筑的顶边(就是上面那条边,原谅我语文表达不好),而顶边则是由左右端点确定的,所以我们只需要存储每个建筑的左右端点。考虑如何存储端点,应该将端点按照x坐标值由小到大排序,同时如果x相等,对于左端点,按照高度由低到高;对于右端点,按照高度由高到低排序。考虑使用什么数据结构存储端点,由于排序以及后面访问端点时,需要知道是左端点还是右端点。所以,我们建立Node类,使用vector存储点,同时重载sort函数进行排序(想到这里,就觉得好麻烦有没有(>﹏<))
2、将端点排序储存之后,我们一个一个访问端点。如果是左节点,就记录高度信息,如果高度大于之前的高度,就说明这个节点是个拐点,记录在ans(vector<pair<int, int>>类型)中;遇到右节点时,就需要将对应的左节点的高度删掉。
于是我们需要存储高度信息,而且还要实现按照大小排序的功能。到底用什么数据结构呢?好蛋疼......set不可以,可能会重复;vector没有排序;通过查文档,发现了priority_queue(好像老师上课讲过,果然没有好好听课0.0),但是这个没法删除,简直B了狗了,可能还需要flag标记要删除的信息,于是有需要map将flag映射到高度信息(果然是数据结构的题。。。。);继续找,发现了multiset,就决定是你了!我们用cur,pre分别代表当前及之前的最大高度,最终得到ans,就是结果。
代码如下:
class Solution {
private:
struct node {
int x, y;
string type;
node(int _x, int _y, string _type) : x(_x), y(_y), type(_type) {}
};
public:
vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings)
{
vector<node> height;
for (int i = 0; i < buildings.size(); i++)
{
height.push_back(node(buildings[i][0], buildings[i][2], "LEFT"));
height.push_back(node(buildings[i][1], buildings[i][2], "RIGHT"));
}
sort(height.begin(), height.end(),f);
multiset<int> heap;
heap.insert(0);
vector<pair<int, int>> res;
int pre = 0, cur = 0;
for (int i = 0; i < height.size(); i++)
{
if (height[i].type == "LEFT")
{
heap.insert(height[i].y);
}
else
{
heap.erase(heap.find(height[i].y));
}
cur = *heap.rbegin();
if (cur != pre)
{
res.push_back({height[i].x, cur});
pre = cur;
}
}
return res;
}
static bool f(const node &a, const node &b)
{
if (a.x != b.x)
return a.x < b.x;
else if (a.type == "LEFT" && b.type == "LEFT")
return a.y > b.y;
else if (a.type == "RIGHT" && b.type == "RIGHT")
return a.y < b.y;
else
return a.type == "LEFT";
}
};
感觉解释的挺清楚的,就没有加注释(我不会告诉你其实是因为我懒(¬_¬))。提交到OJ上,已经可以AC。
本来呢,已经结束了,但是看了@bill_liu的博客,深受启发,手动感谢@bill_liu,@GDP。借用朋神的思路,可以将左节点的高度设为负值加入到点的集合中,这样就可以区分左右结点,而且神奇的发现,这样不需要重写sort函数,也符合比较结果(将端点按照x坐标值由小到大排序,同时如果x相等,对于左端点,按照高度由低到高;对于右端点,按照高度由高到低排序),这样我们不需要重新建Node类,只需要pair存储点即可。
修正代码:
class Solution {
public:
vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings)
{
vector<pair<int, int>> height;
for (int i = 0; i < buildings.size(); i++)
{
height.push_back({buildings[i][0], -buildings[i][2]});
height.push_back({buildings[i][1], buildings[i][2]});
}
sort(height.begin(), height.end());
multiset<int> heap;
heap.insert(0);
vector<pair<int, int>> res;
int pre = 0, cur = 0;
for (int i = 0; i < height.size(); i++)
{
if (height[i].second < 0)
{
heap.insert(-height[i].second);
}
else
{
heap.erase(heap.find(height[i].second));
}
cur = *heap.rbegin();
if (cur != pre)
{
res.push_back({height[i].first, cur});
pre = cur;
}
}
return res;
}
};
代码简洁了许多,有没有~
总结:
这个题确实很难,首先需要抽象出来线段,点,以及明确记录的是什么情况下的点。还有就是选取合适的数据结构,包括使用正负区分左右端点。
嗯,就这样了,(撸完大作业)有时间再想别的思路,晚安~~
同刘斌,为何对于这道题,从OJ上的提交数据来看,用python,java等写出来的运行速度比用C,C++写出来的快得多?求大神解答
此博客中的内容均为原创或来自网络,不用做任何商业用途。欢迎与我交流学习,我的邮箱是lsa0924@163.com

浙公网安备 33010602011771号