关于线段的算法汇总

来源:互联网 发布:淘宝智能机器人好用吗 编辑:程序博客网 时间:2024/06/14 21:20

基本知识:

线段树:存储线段的二叉排序树,以start作为key, 同时维护树中最大的右端点值 max

class Node {    int start;    int end;    int max;}
经典应用:query any interval that intersect(overlap) with a given interval. 如果是查询所有与给定线段重合的线段,则查到一个,从树种删去,再查。最后再插回来。复杂度为RlgN 


区间树:区间[l, r]的左右子树分别为[l, (l + r) / 2], [(l + r) / 2 + 1, r],叶节点都是一个元素的区间[a,a]。主要用于查询区间上的属性,比如和,最大值最小值,满足某种条件的元素个数等。

经典应用,给定一个数组,需要高效的查询任意区间的和(最大值,满足某种条件的元素个数等)。对区间建立区间树,然后每次查询都是lgN的。原理是这样,原区间的值,都都是由左右两个子区间的值得来的,类似merge,比如求整个区间的和,可以分别求左右区间的和,然后相加。这是一个递归的过程,递归到只有一个元素。当区间只有一个元素时,和、最大值等都是自己。


一些经典题目:

1. 给定一组飞机的起飞和降落时间,问同时最多几架飞机在飞行?类似的问法,一组火车出发到达时刻表,问最多同时几趟火车在运行?一组括号中最多几层嵌套?

solution:经典的扫描线法,把线段(start, end)拆成点(time, start/end),排序,如果时间值相同,终点在前起点在后。遇到一个起点count++,遇到一个终点count --。

int countOfAirplanes(vector<Interval> &airplanes) {    vector<pair<int, int>> times;    for (auto &it : airplanes) {        times.push_back(make_pair(it.start, 1));        times.push_back(make_pair(it.end, 0));    }    sort(times.begin(), times.end());    int maxNum = 0, curNum = 0;    for (auto &t : times) {        if (t.second == 1) curNum++;        else curNum--;        maxNum = max(maxNum, curNum);    }    return maxNum;}



2. 给定一组线段,对线段进行merge

solution: 按起点排序,

循环不变式:result 是一个merge过有序的线段列表,初始化就是第一个线段。当前线段跟result.里最后一个线段比,当前线段的start肯定晚于result.back()的start, 因为之前已经按start排过序,主要看是否和其end相交:

1)如果小于等于其end,说明有相交,只需要更新上一个选段的终点 result.back().end = max(result.back().end,  cur.end())

2)否则不想交,直接append到result里

vector<Interval> merge(vector<Interval> &intervals) {if (intervals.empty()) return intervals;sort(intervals.begin(), intervals.end(),[](Interval a, Interval b) -> bool { return a.start < b.start;});vector<Interval> output(1, intervals[0]);for(int i = 1; i < intervals.size(); i++) {if (intervals[i].start <= output.back().end)output.back().end = max(output.back().end, intervals[i].end);elseoutput.push_back(intervals[i]);}return output;}

3 给定一组不想交排好序的线段,把一个线段插入其中,并进行必要的merge

思路

1)跳过在前面的、不相交的线段:新线段起点在其终点后面

2)处理重合的,更新线段的起、终点,并且删除原线段:重合条件:新线段终点大于等于其起点(之前已经保证了新线段起点早于其终点)

3)append后面的不相交的线段

版本一:原地插入

vector<Interval> insert(vector<Interval>& intervals, Interval newInterval) {auto i = intervals.begin();while (i != intervals.end() && newInterval.start > i->end) i++;while (i != intervals.end() && newInterval.end >= i->start) {newInterval.start = min(newInterval.start, i->start);newInterval.end = max(newInterval.end, i->end);i = intervals.erase(i);}intervals.insert(i, newInterval);return intervals;}


版本二:用另一个列表保存结果

public ArrayList<Interval> insert(ArrayList<Interval> intervals, Interval newInterval) {ArrayList<Interval> result = new ArrayList<Interval>();int i = 0;// proceding intervalswhile (i < intervals.size() && intervals.get(i).end < newInterval.start) {result.add(intervals.get(i));i++;}//overlaping  intervals, just keeps a longest onefor (; i < intervals.size() && intervals.get(i).start <= newInterval.end; i++) {newInterval.start = Math.min(newInterval.start, intervals.get(i).start);newInterval.end = Math.max(newInterval.end, intervals.get(i).end);}result.add(newInterval);// intervals going behindfor (; i < intervals.size(); i++)result.add(intervals.get(i));return result;}


4 带颜色的刷线段问题,输入是一系列把一个区间刷成某种颜色的操作,(start, end, color),输出最后的状态

分析;和insert 线段有点像,分三个部分:

1)首先要skip前面不相交的部分,toInsert.start > intervals[i].end || toInsert.start == intervals[i].end && toInsert.color != intervals[i].color,这里的条件稍有不同,和上一个线段的end刚好连上,但是颜色不同,也skip。

2) merge的部分:不带颜色的线段的merge,overlap的部分连在一起,就是保留一个最长的线段。带颜色的话,可能是生成1个,2个,3个,两头部分覆盖,中间完全覆盖

3)后面不相交的部分,toInsert.end < intervals[i].start || toInsert.end == intervals[i].start && toInsert.color != intervals[i]








0 0
原创粉丝点击