1897 · 会议室 3(扫描线)

链接:LintCode 炼码 - 更高效的学习体验!

题解:会议室 3

使用扫描线算法。这个题用线段树来做其实挺直观的,复杂度也能做到 nlognnlogn 但是实在是代码量太大了。所以就看看这个题有没有一些没用到的线段树的功能。进而发现这个题目,只需要支持查询,不需要支持修改。因此就大概率存在不用线段树的方法。进一步的看,这个题并不是每次 ask 都要求你马上返回一个结果,而是一口气给你一堆 ask,然后一口气 return 一堆结果。因此想到可以用离线算法的思想。因此我们可以把 ask 里面的 Intervals 也和正常开会的 Intervals 混在一起去排序和处理。排序的方法是,把 Interval 拆开成为起点和终点,位置优先排序,位置相同的终点在前,起点在后。排序之后从左到右处理,也就是扫描线算法了。在扫描的过程中,维护一个 ask_set 代表此时扫描线扫到的询问区间有哪些,那么如果此时 meetings 的个数 >= 了房间个数 rooms,这些 ask_set 里面的区间,就都需要标记为 False(无法安排会议)。

一些细节要处理的是,由于区间的跨度可能很大,有连个地方需要去处理标记情况,一个是真实的会议开始的时间点,一个是当一个 ask 开始的时间点。

1.扫描线:start是1,end是-1,填写端点val

2.排序:先安装val排序,如果val相等则end位-1的排序在前面

**3.**如果当前点是ask,

判断下当前节点是否是开始:

累加sum>=rooms,表示当前不能添加该会议了

<rooms,则将当前的ask添加到ask_set,因为后面可能有房间导致不满足调整

不是ask

对sum += n.flag进行累加

如果sum >= rooms,表示前面ask_set里面的ask都需要标记为false,且清空ask_set(后面可能还不满足,也可能会重复赋值为false)

cpp 复制代码
class Solution {
public:
    /**
     * @param intervals: the intervals
     * @param rooms: the sum of rooms
     * @param ask: the ask
     * @return: true or false of each meeting
     */
    struct Node {
        int val;
        int flag;
        bool ask;
        int index;
        Node(int v, int f, bool a, int i) {
            val = v;
            flag = f;
            ask = a;
            index = i;
        }
    };
    vector<bool> meetingRoomIII(vector<vector<int>>& intervals, int rooms,
                                vector<vector<int>>& ask) {
        // Write your code here.
        int len = intervals.size();
        if (len <= 0) {
            return {};
        }
        vector<Node> nodes;
        for (auto& v : intervals) {
            Node node1(v[0], 1, false, -1);
            Node node2(v[1], -1, false, -1);
            nodes.push_back(node1);
            nodes.push_back(node2);
        }
        for (int i = 0; i < ask.size(); ++i) {
            vector<int>& v = ask[i];
            Node node1(v[0], 1, true, i);
            Node node2(v[1], -1, true, i);
            nodes.push_back(node1);
            nodes.push_back(node2);
        }
        sort(nodes.begin(), nodes.end(), [](Node& a, Node& b) {
            if (a.val < b.val) {
                return true;
            } else if (a.val == b.val && a.flag < b.flag) {
                return true;
            }
            return false;
        });
        unordered_set<int> ask_set;
        vector<bool> result(ask.size(), true);
        int sum = 0;
        for (auto& n : nodes) {
            if (n.ask) {
                if (n.flag > 0) {
                    if (sum >= rooms) {
                        result[n.index] = false;
                    } else {
                        ask_set.insert(n.index);
                    }
                } else {
                    ask_set.erase(n.index);
                }
            } else {
                sum += n.flag;
                if (sum >= rooms) {
                    for (auto index : ask_set) {
                        result[index] = false;
                    }
                    ask_set.clear();
                }
            }
        }
        return result;
    }
};

ask_set.erase(n.index) 确实会删除 ,但它只删除询问结束时 的那个询问。问题在于:在询问结束之前,如果期间发生了多次"满员",这个询问会被反复遍历到。

具体场景

假设有一个询问 A,它的区间是 [1, 100]。在这段时间内,已有会议导致房间多次满员(比如在时刻 10、30、50、70、90 各满员一次)。

扫描过程:

时刻 事件 ask_set 内容 动作
1 A 开始 {A} 加入 A
10 满员 {A} 遍历 {A},标记 A 为 false ❌
30 满员 {A} 再次 遍历 {A},重复标记 ❌
50 满员 {A} 再次 遍历 {A} ❌
70 满员 {A} 再次 遍历 {A} ❌
90 满员 {A} 再次 遍历 {A} ❌
100 A 结束 {} 删除 A

虽然 A 在第 10 时刻就已经被标记为 false,但它直到第 100 时刻才被 erase。中间的每一次满员,都会重新遍历一遍 A。

如果有 M 个这样的询问,每个都跨越多轮满员,时间复杂度就是 O(K × M),K 是满员次数,M 是询问数,这就超时了。

加上 clear() 后

时刻 事件 ask_set 内容 动作
1 A 开始 {A} 加入 A
10 满员 {A} 遍历 {A},标记 false,clear() ✅
30 满员 {} 空,不遍历
50 满员 {} 空,不遍历
... ... {} 不再重复遍历 ✅
100 A 结束 {} erase 无影响

每个询问最多被遍历一次,总复杂度降到 O(N + M)。

核心区别

操作 erase 单独 erase + clear
何时移除询问 询问结束时 询问第一次遇到满员时(或正常结束)
同一询问被遍历次数 多次(每次满员都遍历) 最多 1 次
最坏复杂度 O(K × M) O(N + M)

一句话总结

erase 只在询问自然结束 时才移除它,无法阻止它在存活期间被反复遍历。

clear() 则在第一次满员时就把所有等待中的询问一次性清空,保证每个询问只被处理一次。两者配合,才能避免超时。

相关推荐
All for pursuit.1 小时前
【贪心-2】253.会议室 II
数据结构·c++·算法·leetcode
垆边人似月.1 小时前
华为机考题(一):质数因子
算法·华为
迷途之人不知返1 小时前
算法系列5:位运算
算法
牢姐与蒯2 小时前
c++加餐之位图
c++·算法·哈希
Omics Pro2 小时前
预测性虚拟细胞中显式机理算子
大数据·数据库·人工智能·python·算法·机器学习·自然语言处理
驭渊的小故事2 小时前
算法实战:滑动窗口、素数枚举与字符串模拟(三道经典题解)
java·算法
牢姐与蒯2 小时前
位运算及其oj题
c++·算法
Logic1014 小时前
C语言/数据结构字符串题解:按字符类型重排(数字→字母→符号)——三趟扫描稳定分区
c语言·数据结构·字符串·时间复杂度·计数排序·算法题·ascii
Logic10111 小时前
C语言/数据结构位运算题解:异或XOR找出学习计划中的“独特学习时间“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质