基于房间数量获取多个房间时间段不可用时间段

【算法】:基于可用的房间数量获取多个房间时间段不可用时间

场景:周日有和朋友约打麻将,目前有3个麻将房间,这个房间在2025年3月30日,有四个时间段已经被预约了,如下
第1个麻将预约:09:11-11:41
第2个麻将预约:10:30-12:00
第3个麻将预约: 09:11-13:00
第4个麻将预约: 13:00-14:41

请问你不能预约的时间段是什么?用 java 实现!
思路:

1.按照时间段构造如下的区间树数据结构

static class Event implements Comparable<Event> {
        /**
         * 时间
         */
        Date time;
        /**
         * 开始时间标识 1,结束时间标识-1
         */
        int delta;

        public Event(Date time, int delta) {
            this.time = time;
            this.delta = delta;
        }

        @Override
        public int compareTo(Event o) {
            // 同一时间点,结束事件优先于开始事件
            if (this.time.getTime() == o.time.getTime()) {
                return Integer.compare(o.delta, this.delta);
            }
            return Long.compare(this.time.getTime(), o.time.getTime());
        }
    }

2.构建区间树列表并且进行排序

private static final List<DateRange> intervals = CollUtil.newArrayList(
            new DateRange(DateUtil.parse("2025-03-29 09:11:00"), DateUtil.parse("2025-03-29 11:41:00")),
            new DateRange(DateUtil.parse("2025-03-29 10:30:00"), DateUtil.parse("2025-03-29 12:00:00")),
            new DateRange(DateUtil.parse("2025-03-29 09:11:00"), DateUtil.parse("2025-03-29 13:00:00")),
            new DateRange(DateUtil.parse("2025-03-29 13:00:00"), DateUtil.parse("2025-03-29 14:41:00"))
    );
// 假设有3个地址(房间)
        int roomNum = 3;
        List<Event> events = new ArrayList<>();

        // 将每个时间区间转换为开始和结束事件
        for (DateRange interval : intervals) {
            // 起始计数为+1
            events.add(new Event(interval.start, 1));
            // 终点计数为-1
            events.add(new Event(interval.end, -1));
        }

        // 按时间排序事件
        Collections.sort(events);

3.开始进行算法如下

List<DateRange> result = new ArrayList<>();
        int count = 0;
        Date currentStart = null;

        // 扫描线算法处理事件
        for (Event event : events) {
            count += event.delta;

            // 当达到或超过roomNum个重叠且当前还没有记录重叠区间时,记录起点
            if (count >= roomNum && currentStart == null) {
                currentStart = event.time;
            }

            // 当计数降到roomNum以下且之前已经开始记录,则记录一个重叠区间
            if (count < roomNum && currentStart != null) {
                result.add(new DateRange(currentStart, event.time));
                currentStart = null;
            }
        }

可以理解算法把时间全部放到一根时间线上,每次循环的结果如下

在这里插入图片描述

所以得出冲突三次以上的时间区间是
“start”:“2025-03-29 10:30:00”,“end”:“2025-03-29 11:41:00”

除了这个时间段其他时间均可以预约麻将馆!

完整代码如下:

package com.jczh.shared.meeting.web.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import com.jczh.shared.core.utils.JsonUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

public class TimeOccupied {
    private static final List<DateRange> intervals = CollUtil.newArrayList(
            new DateRange(DateUtil.parse("2025-03-29 09:11:00"), DateUtil.parse("2025-03-29 11:41:00")),
            new DateRange(DateUtil.parse("2025-03-29 10:30:00"), DateUtil.parse("2025-03-29 12:00:00")),
            new DateRange(DateUtil.parse("2025-03-29 09:11:00"), DateUtil.parse("2025-03-29 13:00:00")),
            new DateRange(DateUtil.parse("2025-03-29 13:00:00"), DateUtil.parse("2025-03-29 14:41:00"))
    );

    public static void main(String[] args) {
        // 假设有3个地址(房间)
        int roomNum = 3;
        List<Event> events = new ArrayList<>();

        // 将每个时间区间转换为开始和结束事件
        for (DateRange interval : intervals) {
            // 起始计数为+1
            events.add(new Event(interval.start, 1));
            // 终点计数为-1
            events.add(new Event(interval.end, -1));
        }

        // 按时间排序事件
        Collections.sort(events);

        List<DateRange> result = new ArrayList<>();
        int count = 0;
        Date currentStart = null;

        // 扫描线算法处理事件
        for (Event event : events) {
            count += event.delta;

            // 当达到或超过roomNum个重叠且当前还没有记录重叠区间时,记录起点
            if (count >= roomNum && currentStart == null) {
                currentStart = event.time;
            }

            // 当计数降到roomNum以下且之前已经开始记录,则记录一个重叠区间
            if (count < roomNum && currentStart != null) {
                result.add(new DateRange(currentStart, event.time));
                currentStart = null;
            }
            Console.log("event:{},count:{},currentStart:{},result:{}", JsonUtil.toJson(event), count, DateUtil.format(currentStart, "yyyy-MM-dd HH:mm:ss"), JsonUtil.toJson(result));
        }

        System.out.println(result);
    }

    @ToString
    @Setter
    @Getter
    static class DateRange {
        private final Date start;
        private final Date end;

        public DateRange(Date start, Date end) {
            this.start = start;
            this.end = end;
        }

        @Override
        public String toString() {
            return "[" + start + ", " + end + "]";
        }
    }

    @Setter
    @Getter
    static class Event implements Comparable<Event> {
        /**
         * 时间
         */
        Date time;
        /**
         * 开始时间标识 1,结束时间标识-1
         */
        int delta;

        public Event(Date time, int delta) {
            this.time = time;
            this.delta = delta;
        }

        @Override
        public int compareTo(Event o) {
            // 同一时间点,结束事件优先于开始事件
            if (this.time.getTime() == o.time.getTime()) {
                return Integer.compare(o.delta, this.delta);
            }
            return Long.compare(this.time.getTime(), o.time.getTime());
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值