给定日期范围列表,找到发生最多次数的日期

时间:2017-02-23 15:07:50

标签: java algorithm date

我有Booking的列表,其中包含startDateendDate。我必须找到预订方面最繁忙的一天。

class Booking {
    Date startDate;
    Date endDate;
}

示例:

2016-10-12 to 2016-10-18
2016-10-11 to 2016-10-15
2016-10-13 to 2016-10-14
2016-10-12 to 2016-10-13

从这些日期开始,很明显2016-10-13全部预订了4次。

我想到的解决方案是:

  • 遍历列表中最小startDate到最大endDate的所有日期
  • 记录所有日期的所有预订数量。
  • 最后,返回最大数量的日期。

但这不是有效的解决方案。我怎样才能有效地找到最忙碌的一天?

7 个答案:

答案 0 :(得分:2)

您可以将每个Booking对象转换为数字行中的间隔,然后将该问题视为在数字行上找到最大间隔数所包含的点的问题。

您可以通过附加年,月和日值来将日期转换为数字,如下所示:

2016-10-12 -> 20161012

然后你可以跟随this technique。这是我做的,没有将Date转换为int的部分:

class Main {
    public static int findBusiest (int[] starts, int[] ends) {
        Arrays.sort(starts);
        Arrays.sort(ends);
        int n = starts.length;

        int in = 1, max = 1, time = starts[0];
        int i = 1, j = 0;

        while (i < n && j < n) {
            if (starts[i] <= ends[j]) {
                in++;

                if (in > max) {
                    max = in;
                    time = starts[i];
                }
                i++;
            }
            else {
                in--;
                j++;
            }
        }

        return time;
    }

    public static void main(String[] args) {

        int[] starts = { 20161012, 20161011, 20161013, 20161012 };
        int[] ends = { 20161018, 20161015, 20161014, 20161013 };

        System.out.println(findBusiest(starts, ends));
    }
}

输出:

20161013

答案 1 :(得分:1)

  1. 为简单起见,给每个日期他们的索引(例如,最小日期有索引0,第二天1等等),并初始化填充零的数组
  2. 遍历所有范围和数组中的开始日期索引增量元素, 并减少结束日期。 (例如,如果某个日期 d 满足范围的3倍,而5倍则作为范围的结束,则应该为-2)
  3. 现在,遍历数组开头的所有日期,并将当前元素添加到您的计数器(基本上,日期 d 处的计数器值是它在范围内的频率)< / LI>
  4. 答案是最大计数器值
  5. 算法有效 O(n),其中n是 minDate maxDate 之间的天数

    PS。 Patrick Parker在this post中提到的解决方案也有效,但它需要 O(m * log(m))时间,其中<​​strong> m 是范围数。因此,您应根据任务规范选择解决方案。

答案 2 :(得分:1)

我想指出一个可能让你朝着正确方向前进的财产。

最频繁的日子将是终点(开始日期或结束日期),或者它们将与终点绑定。

因此,如果能够在关联日期找到一天,您只需要考虑落在终点上的天数。

现在,您将如何可靠地增加每个终点的频率?按顺序处理。但是按照开始顺序处理开始和结束是不够的。开始和结束都必须按日期顺序考虑。

答案 3 :(得分:1)

嗯,这很难看,但如果您有stream List

,我们可以使用Booking来完成
class Booking {
      LocalDate start;
      LocalDate end;
}

我们有List

List<Booking> bookings = new ArrayList<>();
bookings.add(new Booking(LocalDate.of(2016, 10, 12),LocalDate.of(2016, 10, 18)));
bookings.add(new Booking(LocalDate.of(2016, 10, 11),LocalDate.of(2016, 10, 15)));
bookings.add(new Booking(LocalDate.of(2016, 10, 13),LocalDate.of(2016, 10, 14)));
bookings.add(new Booking(LocalDate.of(2016, 10, 12),LocalDate.of(2016, 10, 13)));

现在我们可以遍历列表,每次预订都会获得从startend的所有日期:

Stream<LocalDate> dateRanges = bookings.stream().flatMap(booking ->
        Stream.iterate(booking.start, d -> d.plusDays(1))
                .limit(ChronoUnit.DAYS.between(booking.start, booking.end) + 1)
);

我们拥有所有日期,让我们计算每个日期在新流中出现的次数。

Map<LocalDate, Long> datesFrequency = dateRanges.peek(System.out::println).
collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

最后让我们找到格言 - 最常见的日期:

Optional<Map.Entry<LocalDate, Long>> mostFrequent = datesFrequency.entrySet().
stream().max((o1, o2) -> o1.getValue().compareTo(o2.getValue()));

在这种情况下,结果将是可选的[2016-10-13 = 4];

答案 4 :(得分:0)

对于每个{startDate, endDate}记录生成两个元组{startDate,'start'}{endDate, 'end'}。首先对这些日期进行排序,然后对第二个值进行排序,确保endstart之后。 (据我所知,间隔是包容性的,所以你不想在结束前一个预定的那天开始之前对结束预订进行折扣。)

然后按照上述顺序遍历元组,使用每个start递增一个计数器,每个end递减一次,并跟踪到目前为止的最大值。最大值是预订日期最多。

复杂性为O(n * log(n))。

答案 5 :(得分:0)

我不确定这是否是性能最佳的解决方案,但您只需创建一系列日期,检查频率,然后以最高频率返回:

public static Date getMaxOccurence(Booking[] booking) {
    List<Date> dates = new ArrayList<Date>();
    Date max = new Date();
    int freq = 0;
    for (Booking b : booking) {
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(b.getStartDate());

        while (calendar.getTime().before(b.getEndDate())) {
            Date result = calendar.getTime();
            dates.add(result);
            int curr = Collections.frequency(dates, result);
            if (curr > freq) {
                freq = curr;
                max = result;
            }
            calendar.add(Calendar.DATE, 1);
        }
    }
    return max;
}

ideone demo

答案 6 :(得分:0)

虽然这可能不是最有效的方法,但除非您正在处理数百万个日期,否则这将非常快。我在下面的例子中添加了几千个日期,并且在我的系统上没多久。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;

public class Days {
    Days() {
        long startTime = System.nanoTime();
        SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd");
        ArrayList<Booking> dates = new ArrayList<Booking>();
        try {
            dates.add(new Booking(ft.parse("2016-10-14"), ft.parse("2016-10-18")));
            dates.add(new Booking(ft.parse("2016-11-11"), ft.parse("2018-12-15")));
            dates.add(new Booking(ft.parse("2016-10-13"), ft.parse("2016-10-14")));
            dates.add(new Booking(ft.parse("2016-10-5"), ft.parse("2016-10-13")));
            dates.add(new Booking(ft.parse("2016-10-12"), ft.parse("2020-10-13")));
            dates.add(new Booking(ft.parse("2016-10-10"), ft.parse("2018-10-13")));
            dates.add(new Booking(ft.parse("2015-10-12"), ft.parse("2016-11-13")));
            dates.add(new Booking(ft.parse("2016-09-12"), ft.parse("2016-12-13")));
            dates.add(new Booking(ft.parse("2016-08-12"), ft.parse("2016-10-18")));
            dates.add(new Booking(ft.parse("2016-10-01"), ft.parse("2016-10-26")));
            dates.add(new Booking(ft.parse("2016-02-03"), ft.parse("2016-10-28")));
            dates.add(new Booking(ft.parse("2016-10-04"), ft.parse("2016-12-28")));
            dates.add(new Booking(ft.parse("2015-10-05"), ft.parse("2056-10-16")));
            dates.add(new Booking(ft.parse("2012-10-12"), ft.parse("2016-10-14")));
            dates.add(new Booking(ft.parse("2011-10-12"), ft.parse("2017-02-18")));
            dates.add(new Booking(ft.parse("2016-10-06"), ft.parse("2018-10-13")));
            dates.add(new Booking(ft.parse("2016-10-12"), ft.parse("2019-10-13")));
        } catch (ParseException e) {
            e.printStackTrace();
        }

        ArrayList<Date> datesMult = new ArrayList<Date>();

        for(Booking b : dates) {
            datesMult.addAll(b.getDates());
        }

        HashSet<Date> datesOnce = new HashSet<Date>(datesMult);

        int highestCount = -1;
        Date mostUsed = new Date();

        for(Date d : datesOnce) {
            int count = Collections.frequency(datesMult, d);
            if(count > highestCount) {
                highestCount = count;
                mostUsed = d;
            }
        }

        System.out.println("The most common used date is " + ft.format(mostUsed) + " and it got used " + highestCount + " times");
        long endTime = System.nanoTime();

        long duration = (endTime - startTime);
        System.out.println("This took " + duration + " nanoseconds");
    }

    private class Booking {
        Date startDate;
        Date endDate;

        Booking(Date d1, Date d2) {
            startDate = d1;
            endDate = d2;
        }

        public ArrayList<Date> getDates() {
            ArrayList<Date> d = new ArrayList<Date>();
            d.add(startDate);
            Calendar c = Calendar.getInstance();
            while(true) {
                c.setTime(startDate);
                c.add(Calendar.DATE, 1);  // number of days to add
                startDate = c.getTime();
                if(startDate.compareTo(endDate) == -1) {
                    d.add(startDate);
                } else if(startDate.compareTo(endDate) == 0) {
                    d.add(endDate);
                    return d;
                }
            }
        }
    }

    public static void main(String[] args) {
        new Days();
    }
}