day08 422校门外的树(区间合并)

这篇博客介绍了如何使用两种算法解决一个关于马路树移除的问题。给定一段马路和一些用于建设地铁的区域,任务是计算移除区域内的树后剩余的树的数量。算法1采用模拟法,通过遍历每个区域并更新一个表示树状态的布尔数组,最后统计未被移除的树的数量。算法2利用区间合并思想,首先按区间左端点排序,然后逐个合并区间,更新剩余树的数量。这两种方法分别具有O(ML)和O(MlogM)的时间复杂度。博客提供了完整的Java代码实现,展示了这两种算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

422. 校门外的树

某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是111米。

我们可以把马路看成一个数轴,马路的一端在数轴000的位置,另一端在LLL的位置;数轴上的每个整数点,即0,1,2,……,L0,1,2,……,L012L,都种有一棵树。

由于马路上有一些区域要用来建地铁。

这些区域用它们在数轴上的起始点和终止点表示。

已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。

现在要把这些区域中的树(包括区域端点处的两棵树)移走。

你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入格式
输入文件的第一行有两个整数LLLMMMLLL代表马路的长度,MMM代表区域的数目,LLLMMM之间用一个空格隔开。

接下来的MMM行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

输出格式
输出文件包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。

数据范围
1≤L≤10000,1≤L≤10000,1L10000,
1≤M≤1001≤M≤1001M100
输入样例:

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);
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值