See the original problem on HackerRank.
Solutions
This problem is not particularly difficult, but it does require combining a few key ideas. One of the main challenges depends on the programming language used, since date manipulation is involved. While all mainstream languages provide adequate tools to handle dates, practical difficulties may still arise.
At the time of writing, the latest C++ compiler available on HackerRank is outdated and does not support some of the more recent additions to std::chrono. As a result, solving this problem in C++ requires extra care and more manual handling of dates. For this reason, the C++ solutions are presented at the bottom of this page, as they are slightly more involved compared to those in other languages.
We have encountered a similar problem already at Coding Gym: “The lucky employee”. The core idea is the same, but instead of integer ranges, we now deal with date intervals.
The approach to tackle these two problems is called sweep line, a general technique used to solve problems involving intervals, ranges, or events over an ordered domain (such as time, coordinates, or dates). The idea is to transform each interval into a set of discrete events (such as a start and an end), sort these events, and then scan them in order while maintaining a running state.
As the sweep line “moves” from left to right across the domain, it updates a counter or data structure to reflect how many intervals are currently active. This makes it possible to efficiently compute quantities such as the maximum number of overlapping intervals, the total covered length, or the first point where a given condition is met.
However, the sorting part is not really necessary in our case as we’ll see in a moment.
Consider the following ranges:
|
|
The most frequent value can be computed efficiently using a difference array:
- the start of each range contributes \(+1\) to the counter at that position
- the end of each range contributes \(-1\) to the counter of the next position
Applying this rule gives:
|
|
The actual frequencies are obtained by computing the prefix sum of this array:
|
|
At this point, solving the problem reduces to finding the maximum value in the prefix sum array.
The same pattern applies to this challenge as well. The only difference is that instead of numeric indices, we are working with date spans. Therefore, the key step is to map each date to a numeric index, allowing us to track frequencies using the same prefix sum technique.
A convenient index to use is the number of days elapsed since the minimum possible date. According to the problem constraints, 2018-01-01 is the earliest date, so it can safely be used as the reference point (index 0).
Likewise, the maximum possible date is 2020-12-31, which allows us to determine the required size of the frequency array. The array length should be the number of days between the minimum and maximum dates plus 2: one extra position is needed because the date ranges are inclusive; another extra position ensures that the decrement applied to the last possible date does not go out of bounds.
With this mapping, each date can be converted into an integer index, and the solution can be applied exactly as in the integer-range case. This is the code in Python:
|
|
Here is a similar idea in C#:
|
|
This solution is linear in time (\(O(N + D)\), where \(N\) is the number of reservations and \(D\) is the number of days in the date range, that is actually constant here). It is the fastest and most efficient solution when the domain is small and contiguous.
Sparse domain
If the domain is very large or sparse (for example, if the date intervals are far apart or unbounded), an array-based approach may no longer be practical as we can’t exploit this constraint (=opportunity) anymore. In such cases, we can replace the array with a sorted map that stores only the dates where frequency changes occur, or sort a list later (we’ll see this approach afterwards).
Although dates are represented as strings, we still need to treat them as actual dates in order to compute the day immediately following the end of each reservation. For this reason, we temporarily convert date strings to datetime objects when computing the next day.
As before, we accumulate +1 at the start date and -1 at the day after the end date. The most popular day is then found by computing the prefix sum over the dates in chronological order. In Python, defaultdict(int) is convenient because it automatically initializes missing keys to 0. The final iteration explicitly uses sorted() to ensure dates are processed in order:
|
|
The time complexity here is \(O(N log N)\) due to sorting the dates. It uses less memory for sparse or large domains, but is slower because of map operations and sorting.
Alternatively, we convert each reservation interval into two pairs: one increases the active count, and the other decreases it. All pairs are stored in a list and then sorted chronologically. By scanning the pairs in order and maintaining a running count of active reservations, we can track the maximum overlap and record the earliest date at which it occurs. In the sweep line approach described earlier, each pair is actually called event. Here is the code:
|
|
The complexity in this case is still \(O(N log N)\) due to sorting.
C++ Solutions
As anticipated, the C++ compiler currently available on HackerRank does not support the latest C++20 additions to std::chrono. Because of this limitation, writing a clean and idiomatic C++ solution becomes more challenging, especially when compared to other languages.
To illustrate the intended approach, the following implementation shows how the solution would look using the modern std::chrono facilities. This version closely mirrors the array-based strategy discussed earlier: dates are mapped to integer indices, a difference array is used to track frequency changes, and a prefix sum is applied to determine the most popular day:
|
|
For clarity, the prefix sum and maximum search are performed using std::partial_sum followed by std::max_element. These two steps could be merged into a single loop to avoid iterating twice over the array, but they are kept separate here for readability and brevity.
When latest additions to std::chrono are unavailable, we can still solve the problem efficiently using the old C tm structure. The idea is:
- pick a fixed origin date (
2018-01-01) and represent each date as the number of days since this origin; - build the difference array as before (for each reservation, add
+1at the start index and-1at the day after the end index) - compute the prefix sum to determine the number of active reservations for each day
- track the maximum value and return the corresponding date by converting the index back to a string.
This approach is still linear and uses \(O(D)\) memory, where \(D\) is the total number of days in the domain. It handles leap years correctly via mktime and avoids complex date arithmetic:
|
|
Another approach consists in using a std::map, similarly of previous solutions (\(O(N log N)\)).The only function that we miss is that to calculate the next day for any given date. Doing this manually is straightforward and provides a good opportunity to practice handling leap years and month lengths:
|
|
This solution works without any domain constraints.