【算法】:基于可用的房间数量获取多个房间时间段不可用时间
场景:周日有和朋友约打麻将,目前有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());
}
}
}