关于线段的算法汇总

基本知识:

线段树:存储线段的二叉排序树,以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 &airplanes) {
    vector> 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 merge(vector &intervals) {
	if (intervals.empty()) return intervals;
	
	sort(intervals.begin(), intervals.end(),[](Interval a, Interval b) -> bool { return a.start < b.start;});
	vector 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);
		else
			output.push_back(intervals[i]);
	}
	return output;
}

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

思路

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

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

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

版本一:原地插入

vector insert(vector& 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 insert(ArrayList intervals, Interval newInterval) {
	ArrayList result = new ArrayList();

	int i = 0;
	// proceding intervals
	while (i < intervals.size() && intervals.get(i).end < newInterval.start) {
		result.add(intervals.get(i));
		i++;
	}
	//overlaping  intervals, just keeps a longest one
	for (; 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 behind
	for (; 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]








你可能感兴趣的:(同类问题汇总,算法)