算法:最大线段重合问题

最大线段重合问题

题目描述

给定很多线段,每条线段都有两个数组 [start, end],表示线段的开始位置和结束位置,左右都是闭区间。规定:

  • 线段开始和结束位置一定都是整数值;
  • 线段重合区域的长度必须 >=1 (比如(1,3) 和 (3,5) 不算重合)

返回线段最多重合区域中,包含了几条线段。

类似的题目,最多需要准备多少间教室

题目解析

暴力

  • 先找到所有线段中开始位置最小的min和结束位置最大的max,在这个范围中每隔0.5试探一次,即查询包含1.5的线段树、包含2.5的线段树…之所以能够这样,是因为重合区域必须>=1,而且所有线段开始和结束位置都是整数,所以如果有重合区域,那么一定包含某个.5,考察每个.5被包含的线段数,结果就是这些线段中的最大值,即以0.5进行枚举
  • 时间复杂度:有max - min个 .5,针对每个 .5 都要遍历所有的线段,所以总的时间复杂度为O((max−min)∗N)
  • 假设线段起始的范围都是 100,而线段数是 1 0 4 10^4 104,那么这个方法的时间复杂度在 1 0 5 10^5 105 , 小于 1 0 8 10^8 108 ,所以是能过OJ的。
int maxCover1(std::vector<std::vector<int>>  lines){
    int mi = INT32_MAX, ma = INT32_MIN, size = lines.size();
    for (int i = 0; i < size; ++i) {
        mi = std::min(mi, lines[i][0]);
        ma = std::max(ma, lines[i][1]);
    }

    int max_cover = 0;
    double p = mi + 0.5;
    while (p < ma) {
        int cur_cover = 0;
        for (int i = 0; i < size; ++i) {
            if(lines[i][0] < p &&  p < lines[i][1]){
                ++cur_cover;
            }
        }
        max_cover = std::max(max_cover, cur_cover);
        p += 1;
    }
    return max_cover;
}

堆排序

任何一个重合区域,重合区域的左边界一定是某个线段的左边界,因此,以线段左边界为基点,找到所有包含此基点的线段,放入最小堆中,堆中元素个数就是重合树

思路:

(1)先将线段按照左边界大小排序,以便从左往右验证每个左边界,做到不重不漏

(2)每次将该线段右边界值添加到最小堆中,但是压入之前要先弹出小于左边界的所有元素

  • 之所以要先弹出,是要剔除不重合的线段
    • 如果之前线段右边界小于基点,说明之前线段一定与基点所在线段不重叠
  • 之所以要压入:
    • 重合线段包含自身
    • 映射下个左基点,是否重合
  • 统计每个基点对应的最小堆数量,然后进行比较,找出最大值

在这里插入图片描述
举个例子,现在给定数组[1、7],[2,3]、[4,6]、[4,5]

  • 先根据每条线段的 start 进行从小到大的排序,然后准备一个小根堆,用于存放每条线段的 end。
  • 现在遍历到了第一条线段[1,7]
    • 先先问小根堆,弹出所有 <=1 的数,由于这是第一条线段,没有 <=1 的数,所以直接将 end = 7 放入小根堆中
    • 此时小根堆中只有 7 这个数,所以 [1,7] 这条线段对应的答案是1;【解释:答案1的含义是如何重合区域必须以 [1,7] 的 1 为左边界的话,有多少条线段会越过这个 1 往右延伸】
  • 下一条线段 [2,3],start = 2,将小根堆中 <=2 的数都弹出,而当前小根堆中的数是 7,没有符合条件的数,所以将 end = 3放入小根堆中,此时小根堆中的数为 (3,7),所以 [2,3] 这条线段对应的答案是 2;【解释:之所以要将 <=2 的数弹出,也就是 end <= 2 的数弹出,因为这样的线段是无法越过2的,答案为2的意思是以[2,3] 的 2 为左边界,有多少条线段可以穿过来】
  • 下一条线段[4,6],start = 4,小根堆(3,7),弹出 <= 4 的 3,然后将 end = 6 放入小根堆中,此时小根堆(6,7),size = 2,所以 [4,6] 这条线段对应的答案是 2;【解释:小根堆中的3被弹出,因为这条end = 3的线段无法穿过[4,6] 中的 4,所以要将它剔除,答案为2表示重合边界以 [4,6] 的 4 为左边界,有2条线段可以穿过来】
  • 下一条线段 [4,5], start = 4,小根堆(6,7),没有 <= 4 的数,直接将 end = 5 放入小根堆中,此时小根堆(5,6,7),size = 3,所以 [4,5] 这条线段对应的答案是 3;

int maxCover2(std::vector<std::vector<int>> lines){
    //将所有线段按照开始位置从小到大排序
    std::sort(lines.begin(), lines.end(), [](std::vector<int> &l, std::vector<int>& r){
        return l < r;
    });
    
    // 小根堆
    int max_cover = 0;
    std::priority_queue<int , std::vector<int>, std::greater<>> heap;
    for (int i = 0; i < lines.size(); ++i) {

        // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出
        while (!heap.empty() && heap.top() <= lines[i][0]){
            heap.pop();
        }
        heap.emplace(lines[i][1]);
        max_cover = std::max(max_cover, (int)heap.size());
    }

    return max_cover;
}

  • 复杂度的估计:
    • 每条线段的结尾位置最多进一次小根堆,出一次小根堆;而有N条线段,加入小根堆最多N次,弹出小根堆也最多N次
    • 调整小根堆的时间复杂度是O(logN)
    • 所以时间复杂度是O(N * logN)

测试

std::default_random_engine e;
std::vector<std::vector<int>> generateLines(int N, int L, int R){
    std::uniform_int_distribution<int> distS(0, N);
    std::uniform_int_distribution<int> distV(L, R);
    int size = distS(e);
   std::vector<std::vector<int>> ans;
    for (int i = 0; i < size; ++i) {
        int v1 = distV(e), v2 = distV(e);
        if(v1 == v2){
            v2 = v1 + 1;
        }else if(v1 > v2){
            std::swap(v1, v2);
        }

        ans.push_back({v1, v2});
    }
    return ans;
}

void print(std::vector<std::vector<int>> lines){
    printf("[");
    for (auto & line : lines) {
        printf("[%d, %d]\t\t", line[0], line[1]);
    }
    printf("]\n");
}


int main() {
    e.seed(time(NULL));
    int N = 100;
    int L = 0;
    int R = 200;
    int testTimes = 10000;

    std::vector<std::vector<int>> lines {{1, 7}, {2, 3}, {4, 6}, {4, 5}};




    for(int i = 0; i < testTimes; i++){
        lines = generateLines(N, L, R);
        int ans1 = maxCover1(lines);
        int ans2 = maxCover2(lines);
        if(ans1 != ans2){
            print(lines);
            printf(" error ! ans1 = %d, ans2 = %d\n\n", ans1, ans2);
            return -1;
        }
    }
    printf("ok");

    return 0;
}

最少需要几个教室

有若干个活动,第i个开始时间和结束时间是[Si,fi),同一个教室安排的活动之间不能交叠,求要安排所有活动,最少需要几个教室?

题目解析

本题的实质是要求重合区间最多时,有几个区间。

思路:

  • 先根据end进行排序
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值