422. 校门外的树
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是111米。
我们可以把马路看成一个数轴,马路的一端在数轴000的位置,另一端在LLL的位置;数轴上的每个整数点,即0,1,2,……,L0,1,2,……,L0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。
这些区域用它们在数轴上的起始点和终止点表示。
已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。
现在要把这些区域中的树(包括区域端点处的两棵树)移走。
你的任务是计算将这些树都移走后,马路上还有多少棵树。
输入格式
输入文件的第一行有两个整数LLL和MMM,LLL代表马路的长度,MMM代表区域的数目,LLL和MMM之间用一个空格隔开。
接下来的MMM行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
输出格式
输出文件包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
数据范围
1≤L≤10000,1≤L≤10000,1≤L≤10000,
1≤M≤1001≤M≤1001≤M≤100
输入样例:
500 3
150 300
100 200
470 471
输出样例:
298
算法1
(模拟,数组遍历) O(ML)O(ML)O(ML)
定义一个长度为 L+1L+1L+1 的布尔数组,表示每棵树的状态。
true
表示已经被移走;false
表示未被移走;
对于每次移动树木的操作 [Li,Ri][L_i,R_i][Li,Ri],直接循环一遍,将布尔数组中从 LiL_iLi,到 RiR_iRi 这段赋值为true
。
最后统计值为 false
的数量即可。
时间复杂度分析
对于每次移动树木的操作,最坏情况下区间长度是 O(L)O(L)O(L),因此计算量是 O(L)O(L)O(L),一共有 MMM 次操作,因此总时间复杂度是 O(ML)=100×10000=106O(ML)=100×10000=10^6O(ML)=100×10000=106。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int L = scanner.nextInt();//树从0-L,故共有L+1棵树
int M = scanner.nextInt();
boolean[] st = new boolean[L + 1];//state数组,用于标记该点是否要用于建地铁
while(M-- !=0){
int l = scanner.nextInt();
int r = scanner.nextInt();
for(int i = l;i <= r;i++){//[l,r]用于建地铁
st[i] = true;
}
}
int cnt = 0;//用于建地铁区域占多少棵树
for(int i = 0;i < st.length;i++){
if(st[i]){
cnt += 1;
}
}
System.out.println(L + 1 - cnt);//输出还剩下多少棵树
}
}
算法2
(区间合并) O(MlogM)O(MlogM)O(MlogM)
先求出所有移动树木的操作的区间的并集,那么马路上剩余部分即为最终剩下树木的部分。
求所有区间的并集可以使用区间合并算法。
时间复杂度分析
区间合并算法的时间复杂度是 O(MlogM)O(MlogM)O(MlogM),其中 MMM 是区间数量。
区间合并算法思想
1:
把数组按照左端点从小到大的顺序排序
2:
维护一个区间(我记为基准区间),和下一个区间进行对比,会分为三种情况
- 第一种:当前的区间完全被基准区间所覆盖,此时基准区间无需做任何变化。
- 第二种:当前的区间与基准区间有重合的部分,且当前区间右端点比基准区间右端点大,这个时候我们就更新基准区间的右端点为当前区间的右端点,即将两个区间进行了合并。
- 第三种:当前的区间左端点大于上一个区间的右端点;此时的话,我们维护的基准区间要更新为当前的区间,因为上一个基准区间的长度已经达到最长了,之后的区间与其并无任何重合的部分了。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int L = scanner.nextInt();//树从0-L,故共有L+1棵树
int M = scanner.nextInt();
List<int[]> list = new ArrayList<>();//用于存储每段区间
for(int i = 0 ;i < M;i++){
int[] segment = {scanner.nextInt(),scanner.nextInt()};
list.add(segment);
}
list.sort(new Comparator<int[]>() {//按照每段区间的左端点由小到大排序
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0];
}
});
//最开始的基准区间
int start = list.get(0)[0];
int end = list.get(0)[1];
int res = L + 1;//原始树的数量
for(int i = 1;i < list.size();i++){
if(list.get(i)[0] <= end){//当前区间左端点在基准区间内,说明两个区间有重合部分
end = Math.max(list.get(i)[1],end);//更新基准区间的右端点为两个区间右端点较大的那个
}else{//当前区间与基准区间无重合部分,说明第一段基准区间合并结束,更新当前区间为新基准区间
//让结果减去上个区间所包含的树的数量(即res = res-(end - start +1))
res -= end - start +1;
start = list.get(i)[0];
end = list.get(i)[1];
}
}
res -= end - start + 1;//减去最后一段合并的基准区间包含的树的数量
System.out.println(res);
}
}