3184. Count Pairs That Form a Complete Day I
Problem Description
You are given an integer array hours
where each element represents a time duration in hours. Your task is to find the number of pairs of indices (i, j)
where i < j
and the sum hours[i] + hours[j]
forms a complete day.
A complete day is defined as any time duration that is an exact multiple of 24 hours. For example:
- 24 hours = 1 complete day
- 48 hours = 2 complete days
- 72 hours = 3 complete days
- And so on...
The solution uses a counting approach with modular arithmetic. For each hour value x
in the array, we need to find how many previously seen values can pair with x
to form a multiple of 24.
The key insight is that two numbers sum to a multiple of 24 if and only if their remainders when divided by 24 sum to either 0 or 24. So for a value x
, we need to find values that have a remainder of (24 - (x % 24)) % 24
when divided by 24.
The algorithm maintains a counter cnt
that tracks how many times each remainder (0 through 23) has appeared so far. For each new hour value x
:
- We calculate what remainder would complement
x
to form a multiple of 24:(24 - (x % 24)) % 24
- We add the count of previously seen values with this complementary remainder to our answer
- We increment the count for
x % 24
in our counter for future pairs
This approach ensures we only count valid pairs where i < j
since we only look at previously processed elements when finding pairs for the current element.
Intuition
When we need to find pairs that sum to a multiple of 24, we can think about this problem in terms of remainders. If two numbers add up to a multiple of 24, what can we say about their remainders when divided by 24?
Let's consider two numbers a
and b
. If a + b
is divisible by 24, then (a % 24) + (b % 24)
must equal either 0 or 24. This is because:
- If both remainders are 0, their sum is 0
- If the remainders sum to 24, they "complete" another full cycle of 24
- Any sum greater than 24 would mean we have another full cycle plus some remainder
So for any hour value x
with remainder r = x % 24
, we need to find values with remainder 24 - r
to form a complete day. But there's a special case: when r = 0
, we need another value with remainder 0 (not 24). This is why we use the formula (24 - (x % 24)) % 24
- the outer modulo handles the case when x % 24 = 0
.
The natural approach would be to check every pair, but that would be O(n²). Instead, we can be smarter: as we traverse the array, we keep track of how many times we've seen each possible remainder (0 through 23). When we encounter a new value, we immediately know how many previous values it can pair with by looking up the count of its complementary remainder.
This transforms our problem from "find all pairs" to "for each element, count how many previous elements can pair with it" - a classic pattern that reduces time complexity from O(n²) to O(n) by trading space for time with a frequency counter.
Solution Approach
The implementation uses a counting approach with a hash table (Counter in Python) to efficiently track remainders and find valid pairs.
Data Structure:
cnt
: A Counter (hash table) that stores the frequency of each remainder value (0 through 23)ans
: An accumulator for the total number of valid pairs
Algorithm Steps:
-
Initialize the counter and answer
- Create an empty Counter
cnt
to track remainder frequencies - Set
ans = 0
to accumulate the pair count
- Create an empty Counter
-
Process each hour value For each hour value
x
in the array:a. Find complementary pairs
- Calculate the complementary remainder:
(24 - (x % 24)) % 24
- This gives us the remainder that, when added to
x % 24
, results in a multiple of 24 - Add
cnt[(24 - (x % 24)) % 24]
to our answer - this counts all previous values that can pair withx
b. Update the counter
- Increment
cnt[x % 24]
by 1 - This records the current value's remainder for future pairs
- Calculate the complementary remainder:
-
Return the result
- After processing all elements,
ans
contains the total number of valid pairs
- After processing all elements,
Why this works:
- By processing elements left to right and only looking at previous counts, we automatically ensure
i < j
for all pairs - The formula
(24 - (x % 24)) % 24
correctly handles all cases:- If
x % 24 = 10
, we need remainder14
(since 10 + 14 = 24) - If
x % 24 = 0
, we need remainder0
(since 0 + 0 = 0, which is divisible by 24)
- If
- Time complexity: O(n) - single pass through the array
- Space complexity: O(1) - at most 24 different remainder values to store
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with hours = [12, 12, 30, 24, 24]
.
Initial State:
cnt = {}
(empty counter)ans = 0
Step 1: Process hours[0] = 12
- Calculate remainder:
12 % 24 = 12
- Find complement:
(24 - 12) % 24 = 12
- Check counter:
cnt[12] = 0
(no previous values with remainder 12) - Add to answer:
ans = 0 + 0 = 0
- Update counter:
cnt[12] = 1
- State:
cnt = {12: 1}
,ans = 0
Step 2: Process hours[1] = 12
- Calculate remainder:
12 % 24 = 12
- Find complement:
(24 - 12) % 24 = 12
- Check counter:
cnt[12] = 1
(found one previous 12!) - Add to answer:
ans = 0 + 1 = 1
(pair found: indices 0,1) - Update counter:
cnt[12] = 2
- State:
cnt = {12: 2}
,ans = 1
Step 3: Process hours[2] = 30
- Calculate remainder:
30 % 24 = 6
- Find complement:
(24 - 6) % 24 = 18
- Check counter:
cnt[18] = 0
(no previous values with remainder 18) - Add to answer:
ans = 1 + 0 = 1
- Update counter:
cnt[6] = 1
- State:
cnt = {12: 2, 6: 1}
,ans = 1
Step 4: Process hours[3] = 24
- Calculate remainder:
24 % 24 = 0
- Find complement:
(24 - 0) % 24 = 0
- Check counter:
cnt[0] = 0
(no previous values with remainder 0) - Add to answer:
ans = 1 + 0 = 1
- Update counter:
cnt[0] = 1
- State:
cnt = {12: 2, 6: 1, 0: 1}
,ans = 1
Step 5: Process hours[4] = 24
- Calculate remainder:
24 % 24 = 0
- Find complement:
(24 - 0) % 24 = 0
- Check counter:
cnt[0] = 1
(found one previous 24!) - Add to answer:
ans = 1 + 1 = 2
(pair found: indices 3,4) - Update counter:
cnt[0] = 2
- Final state:
cnt = {12: 2, 6: 1, 0: 2}
,ans = 2
Result: 2 pairs form complete days:
- Pair (0,1):
12 + 12 = 24
✓ - Pair (3,4):
24 + 24 = 48
✓
The algorithm correctly identifies both pairs by tracking remainders and finding complements.
Solution Implementation
1class Solution:
2 def countCompleteDayPairs(self, hours: List[int]) -> int:
3 # Dictionary to store frequency of remainders when divided by 24
4 remainder_count = Counter()
5 # Total number of valid pairs
6 total_pairs = 0
7
8 # Iterate through each hour value
9 for hour in hours:
10 # Calculate the remainder when current hour is divided by 24
11 current_remainder = hour % 24
12
13 # Calculate what remainder we need to form a complete day (multiple of 24)
14 # If current_remainder is 0, we need another 0
15 # If current_remainder is non-zero, we need (24 - current_remainder)
16 needed_remainder = (24 - current_remainder) % 24
17
18 # Add count of previously seen hours that can pair with current hour
19 total_pairs += remainder_count[needed_remainder]
20
21 # Update the count for current hour's remainder
22 remainder_count[current_remainder] += 1
23
24 return total_pairs
25
1class Solution {
2 public int countCompleteDayPairs(int[] hours) {
3 // Array to count occurrences of each remainder when divided by 24
4 int[] remainderCount = new int[24];
5 int pairCount = 0;
6
7 for (int hour : hours) {
8 // Calculate the remainder when current hour is divided by 24
9 int currentRemainder = hour % 24;
10
11 // Find the complement remainder that would sum to 24 (a complete day)
12 // If currentRemainder is 0, we need another 0 to form a complete day
13 // Otherwise, we need (24 - currentRemainder) to form a complete day
14 int complementRemainder = (24 - currentRemainder) % 24;
15
16 // Add the count of previously seen complement remainders to our answer
17 pairCount += remainderCount[complementRemainder];
18
19 // Increment the count for the current remainder
20 remainderCount[currentRemainder]++;
21 }
22
23 return pairCount;
24 }
25}
26
1class Solution {
2public:
3 int countCompleteDayPairs(vector<int>& hours) {
4 // Array to count occurrences of each remainder when divided by 24
5 // remainder_count[i] stores how many hours have remainder i when divided by 24
6 int remainder_count[24]{};
7
8 // Variable to store the total number of valid pairs
9 int total_pairs = 0;
10
11 // Iterate through each hour value in the input array
12 for (int current_hour : hours) {
13 // Calculate the remainder needed to form a complete day (multiple of 24)
14 // If current_hour % 24 = r, we need (24 - r) % 24 to sum to 24
15 int required_remainder = (24 - current_hour % 24) % 24;
16
17 // Add the count of previously seen hours with the required remainder
18 // These will form valid pairs with the current hour
19 total_pairs += remainder_count[required_remainder];
20
21 // Increment the count for the current hour's remainder
22 // This will be used for pairing with future hours
23 ++remainder_count[current_hour % 24];
24 }
25
26 return total_pairs;
27 }
28};
29
1/**
2 * Counts the number of pairs of hours that form complete days (sum is divisible by 24)
3 * @param hours - Array of hour values
4 * @returns Number of valid pairs that form complete days
5 */
6function countCompleteDayPairs(hours: number[]): number {
7 // Array to store frequency of each remainder when divided by 24
8 // Index represents the remainder (0-23), value represents count
9 const remainderCount: number[] = Array(24).fill(0);
10
11 // Counter for valid pairs
12 let pairCount: number = 0;
13
14 // Iterate through each hour value
15 for (const currentHour of hours) {
16 // Calculate the remainder needed to form a complete day with current hour
17 // For currentHour % 24 = r, we need (24 - r) % 24 to make sum divisible by 24
18 const neededRemainder: number = (24 - (currentHour % 24)) % 24;
19
20 // Add count of previously seen hours that can pair with current hour
21 pairCount += remainderCount[neededRemainder];
22
23 // Increment the count for current hour's remainder
24 remainderCount[currentHour % 24]++;
25 }
26
27 return pairCount;
28}
29
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array hours
. This is because the algorithm iterates through the array exactly once, and for each element, it performs constant-time operations: computing the modulo, looking up a value in the counter, updating the answer, and incrementing the counter.
The space complexity is O(C)
, where C = 24
. The Counter dictionary cnt
stores at most 24 different keys (representing hours modulo 24, from 0 to 23). Since there are only 24 possible remainders when dividing by 24, the space used by the counter is bounded by this constant, regardless of the input size n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Handling of Zero Remainder Case
The Pitfall:
A common mistake is writing the complementary remainder calculation as simply 24 - (hour % 24)
without the outer modulo operation. This fails for the case when hour % 24 = 0
.
Why it's wrong:
- When
hour % 24 = 0
, we need to pair with another value that has remainder 0 - But
24 - 0 = 24
, which is not a valid remainder (remainders range from 0 to 23) - This would cause us to look for
remainder_count[24]
, which doesn't exist and returns 0, missing valid pairs
Example of incorrect code:
# WRONG - Missing outer modulo needed_remainder = 24 - (hour % 24) # When hour % 24 = 0, this gives 24! total_pairs += remainder_count[needed_remainder] # Looks for key 24, which doesn't exist
The Solution:
Always use (24 - (hour % 24)) % 24
to ensure the result is in the valid range [0, 23]:
# CORRECT - Outer modulo ensures valid remainder needed_remainder = (24 - (hour % 24)) % 24 # When hour % 24 = 0: (24 - 0) % 24 = 0 ✓ # When hour % 24 = 10: (24 - 10) % 24 = 14 ✓
2. Counting Pairs in Wrong Order
The Pitfall: Updating the counter before checking for pairs, which would incorrectly allow an element to pair with itself.
Example of incorrect code:
# WRONG - Updates counter before checking pairs for hour in hours: remainder_count[hour % 24] += 1 # Updated first needed_remainder = (24 - (hour % 24)) % 24 total_pairs += remainder_count[needed_remainder] # Now might count self-pairs
Why it's wrong:
If an hour value like 12 appears (12 % 24 = 12, needs remainder 12 to form 24), updating the counter first would allow it to pair with itself, violating the i < j
constraint.
The Solution: Always check for pairs with previously seen elements before updating the counter:
# CORRECT - Check pairs first, then update for hour in hours: needed_remainder = (24 - (hour % 24)) % 24 total_pairs += remainder_count[needed_remainder] # Check previous elements remainder_count[hour % 24] += 1 # Then update for future pairs
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!