标题不是很好,我愿意接受建议。
这是我的基本问题:
我有一组约会,每个约会都有一个开始时间和结束时间。
鉴于该设置,我想要的是一组新的范围[ start_time, end_time ]
适用于n
重叠约会的所有时段。
因此,例如,给定集合(时间戳简化为可读性的小数字)
[
[ 1, 3 ],
[ 2, 4 ],
[ 2, 4 ],
[ 5, 7 ],
[ 6, 8 ],
[ 7, 8 ]
]
...并且假设我想要在其中发生至少3个不同约会的所有范围,结果应为
[
[ 2, 3 ],
[ 6, 7 ]
]
为了减少抽象......
想象一下,我总是在工作人员的3个安装人员身上进行24小时的窗口着色服务。在我的网站上,我想显示所有可用的安装时间。所以我需要隐藏我已经安排了三次预约的时间范围。
不一定要求任何人编写代码 - 但如果有一个众所周知的算法可以解决这类问题,我会很感激。
感谢。
[EDIT]添加了javascript标签,因为我将在Node中实现此功能,但答案不需要在JS中。
[编辑2]我正在寻找一个非常通用的解决方案,所以让我们说约会可以随时开始(没有标准化为小时或30分钟的块)并且可以是任何持续时间
答案 0 :(得分:2)
我认为从输入范围创建直方图,然后迭代直方图定位范围,其中高度大于或等于目标重叠,在本例中为3。
顺便说一下,考虑到你的意见,我认为[6,7]是一个有效的范围 - 我认为它应该是[7,7]。至少我的代码产生的是:)这里有一些Java代码来说明:
public static void main(String[] args)
{
int[][] ranges = {{1,3},{2,4},{2,4},{5,7},{6,8},{7,8}};
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for(int[] range : ranges)
{
min = Math.min(min, range[0]);
max = Math.max(max, range[1]);
}
int[] hist = new int[1+max-min];
for(int[] range : ranges)
for(int i=range[0]; i<=range[1]; i++) hist[i-min]++;
int overlap = 3;
for(int i=0; i<hist.length; i++)
{
int j = i;
while(i<hist.length && hist[i] >= overlap) {i++;}
if(i>j)
System.out.println((j+min) + " : " + (i+min-1));
}
}
输出:
2 : 3
7 : 7
修改强>
我对直方图方法不满意,因为它依赖于整数范围,并且对于长范围而言效率低。在我看来你可以改为对范围端点进行排序,跟踪它们是否在范围的开始或结束,然后遍历端点,保持有效范围的计数器(当你遇到一个开始时递增,当你遇到你时减少)遇到了结束)。当计数器首次超过或低于您的阈值时,在您的情况3中,您输出范围。
我现在看到MBo提出了同样的方法。
这里还有一些代码可以说明:
static class RangeEnd
{
int time;
int delta;
public RangeEnd(int pos, int delta)
{
this.time = pos;
this.delta = delta;
}
}
public static void main(String[] args)
{
int[][] ranges = {{ 1,3},{2,4},{2,4},{5,7},{6,8},{7,8}};
RangeEnd[] ends = new RangeEnd[2*ranges.length];
int i=0;
for(int[] range : ranges)
{
ends[i++] = new RangeEnd(range[0], 1);
ends[i++] = new RangeEnd(range[1], -1);
}
Arrays.sort(ends, new Comparator<RangeEnd>()
{
@Override
public int compare(RangeEnd e1, RangeEnd e2)
{
if(e1.time < e2.time) return -1;
else if(e1.time > e2.time) return 1;
else if (e1.delta > e2.delta) return -1;
else return 1;
}
});
int overlap = 3;
int count = 0;
boolean active = false;
int start = 0;
for(RangeEnd end : ends)
{
count += end.delta;
if(count >= overlap)
{
if(!active)
{
start = end.time;
active = true;
}
}
else if(active)
{
System.out.println(start + " : " + end.time);
active = false;
}
}
}
答案 1 :(得分:1)
制作数组/对列表:{time, flag = +1 for start of interval, -1 for the end of interval}
按时间键排序列表。如果开始/结束标志为tie帐户(如果[1,2]
和[2,3]
之间的间隔不相交,则在开始之前结束)
制作Counter = 0
遍历列表,为每对添加标记Counter
。当Counter
从n-1
更改为n
时 - 输出范围开始,当Counter
从n
更改为n-1
时 - 输出范围结束
答案 2 :(得分:0)
假设你可以按照30分钟的间隔放置东西,然后在#appts = 0的第一个间隔开始,并且在每个间隔点,从现在开始的每个appt递增,并且对于现在结束的每个appt递减。 #appts将始终跟踪当前时间间隔内活动的应用程序数量。
如果你想要真正高效,你可以将每个时间间隔的“开始结束时间”桶“排序”到桶中,然后整个过程将是线性的。但除非你有超级大量的约会,否则你也可以随时查看它们。
答案 3 :(得分:0)
你的时间戳和范围究竟是什么样的?它们是每日/小时/半小时/分钟特定的吗?
这是一个可能的解决方案: 让我们说你的时间戳是特定时间的。 声明一个字典来保存字符串键和int值。密钥将代表小时的时间戳,例如&#34; 08-30-17 23&#34;。价值将是该小时内将要/多少次约会的次数。
现在循环遍历您的范围。对于每个范围,使用另一个循环来完成开始和结束时间之间的小时。对于每个小时,在该字典中为该时间戳增加1的计数(以小时粒度表示)。
最后,您应该在数据中找到每小时的约会次数。如果您在下午5点到6点之间以及指定日期的下午6点和下午7点有三个约会,那么您需要更多逻辑来将其转换为5到7pm范围。