从一组约会中获取重叠时间范围列表

时间:2017-08-31 03:21:34

标签: javascript algorithm

标题不是很好,我愿意接受建议。

这是我的基本问题:

我有一组约会,每个约会都有一个开始时间和结束时间。

鉴于该设置,我想要的是一组新的范围[ 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分钟的块)并且可以是任何持续时间

4 个答案:

答案 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。当Countern-1更改为n时 - 输出范围开始,当Countern更改为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范围。