Facebook Pixel

3185. Count Pairs That Form a Complete Day II

MediumArrayHash TableCounting
Leetcode Link

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. The key insight is that two numbers form a complete day when their sum is divisible by 24.

For each hour value x in the array, we need to find how many previous values can pair with it to form a multiple of 24. If x % 24 = r, then we need a value y such that (r + y % 24) % 24 = 0. This means y % 24 must equal (24 - r) % 24.

The algorithm maintains a counter cnt that tracks how many times each remainder (when divided by 24) has appeared so far. For each new hour x:

  1. Calculate what remainder would complement x to form a complete day: (24 - (x % 24)) % 24
  2. Add the count of previous hours with that remainder to the answer
  3. Update the counter with x's remainder for future pairs

This approach ensures we only count valid pairs where i < j since we only look at previously seen values when processing each element.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem asks us to find pairs that sum to multiples of 24. A naive approach would be to check every possible pair, but that would take O(n²) time. We need something more efficient.

Let's think about what makes two numbers add up to a multiple of 24. If we have two numbers a and b, and (a + b) % 24 = 0, then the remainders of a and b when divided by 24 must complement each other to sum to 24 (or 0).

For example:

  • If a = 26 (remainder 2), we need b to have remainder 22 so that 2 + 22 = 24
  • If a = 48 (remainder 0), we need b to also have remainder 0 so that 0 + 0 = 0
  • If a = 13 (remainder 13), we need b to have remainder 11 so that 13 + 11 = 24

This observation leads us to realize that we don't actually care about the full values - we only care about their remainders when divided by 24. Two numbers with the same remainder will behave the same way when looking for pairs.

So the key insight is: for any hour value x with remainder r (where r = x % 24), it can form a complete day with any previous hour value that has remainder (24 - r) % 24. The extra modulo operation handles the edge case where r = 0, ensuring we get 0 instead of 24.

This suggests we can solve the problem in a single pass: as we iterate through the array, we keep track of how many times we've seen each remainder value. For each new element, we check how many previous elements have the complementary remainder, add that to our answer, then update our count for the current element's remainder.

This way, we naturally ensure i < j because we only pair current elements with previously seen ones, and we achieve O(n) time complexity with O(1) extra space (since there are only 24 possible remainders).

Solution Approach

The solution implements the counting approach using a hash table to track remainder frequencies.

Data Structure Used:

  • Counter(): A Python dictionary subclass that stores the frequency of each remainder value (0-23)
  • ans: An integer to accumulate the total number of valid pairs

Algorithm Steps:

  1. Initialize the counter and answer:

    cnt = Counter()
    ans = 0

    The counter starts empty, and we'll build it up as we process each element.

  2. Process each hour value: For each x in the hours array:

    a. Find complementary remainder:

    ans += cnt[(24 - (x % 24)) % 24]
    • First, calculate x % 24 to get the remainder of current hour
    • Then find what remainder would complement it: 24 - (x % 24)
    • Apply modulo again to handle the case when x % 24 = 0, which gives us (24 - 0) % 24 = 0
    • Look up how many previous hours had this complementary remainder
    • Add this count to our answer

    b. Update the counter:

    cnt[x % 24] += 1
    • Record that we've seen one more hour with remainder x % 24
    • This will be available for pairing with future elements
  3. Return the result: After processing all hours, ans contains the total number of valid pairs.

Why this works:

  • By processing elements left to right and only pairing with previously seen elements, we naturally ensure i < j
  • The modular arithmetic (24 - (x % 24)) % 24 correctly identifies the complementary remainder for any value
  • Using a counter allows O(1) lookups and updates

Time Complexity: O(n) where n is the length of the hours array - we make a single pass through the array Space Complexity: O(1) - the counter can have at most 24 entries regardless of input size

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example 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 hours with remainder 12)
  • Update 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 (one previous hour with remainder 12)
  • Update answer: ans = 0 + 1 = 1 (found pair: indices 0 and 1, since 12 + 12 = 24)
  • 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 hours with remainder 18)
  • Update 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 hours with remainder 0)
  • Update 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 (one previous hour with remainder 0)
  • Update answer: ans = 1 + 1 = 2 (found pair: indices 3 and 4, since 24 + 24 = 48)
  • Update counter: cnt[0] = 2
  • State: cnt = {12: 2, 6: 1, 0: 2}, ans = 2

Final Result: 2 pairs

  • Pair 1: hours[0] + hours[1] = 12 + 12 = 24 (1 complete day)
  • Pair 2: hours[3] + hours[4] = 24 + 24 = 48 (2 complete days)

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_pairs = 0
6      
7        # Iterate through each hour value
8        for hour in hours:
9            # Calculate the remainder when current hour is divided by 24
10            current_remainder = hour % 24
11          
12            # Calculate what remainder we need to form a complete day (sum divisible by 24)
13            # If current_remainder is 0, we need another 0
14            # Otherwise, we need (24 - current_remainder)
15            needed_remainder = (24 - current_remainder) % 24
16          
17            # Add count of previously seen hours that can pair with current hour
18            total_pairs += remainder_count[needed_remainder]
19          
20            # Update the count for current hour's remainder
21            remainder_count[current_remainder] += 1
22      
23        return total_pairs
24
1class Solution {
2    public long countCompleteDayPairs(int[] hours) {
3        // Array to count occurrences of each remainder when divided by 24
4        int[] remainderCount = new int[24];
5        long totalPairs = 0;
6      
7        for (int hour : hours) {
8            // Calculate the remainder needed to form a complete day (24 hours)
9            // For hour % 24 = r, we need (24 - r) % 24 to make a sum divisible by 24
10            int currentRemainder = hour % 24;
11            int complementRemainder = (24 - currentRemainder) % 24;
12          
13            // Add count of previous elements that can pair with current hour
14            totalPairs += remainderCount[complementRemainder];
15          
16            // Update the count for current hour's remainder
17            remainderCount[currentRemainder]++;
18        }
19      
20        return totalPairs;
21    }
22}
23
1class Solution {
2public:
3    long long countCompleteDayPairs(vector<int>& hours) {
4        // Array to count frequency of each remainder when divided by 24
5        // cnt[i] stores count of hours with remainder i when divided by 24
6        int cnt[24]{};
7      
8        // Variable to store the total count of valid pairs
9        long long ans = 0;
10      
11        // Iterate through each hour value
12        for (int x : hours) {
13            // For current hour x, find how many previous hours can pair with it
14            // to form a sum divisible by 24
15            // If x % 24 = r, we need to find hours with remainder (24 - r) % 24
16            // The (24 - x % 24) % 24 handles the case when x % 24 = 0
17            ans += cnt[(24 - x % 24) % 24];
18          
19            // Increment the count for current hour's remainder
20            ++cnt[x % 24];
21        }
22      
23        return ans;
24    }
25};
26
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        // If currentHour % 24 = r, we need (24 - r) % 24 to make a complete day
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: calculating 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 distinct keys since we're storing values modulo 24 (i.e., x % 24 can only produce values from 0 to 23). Therefore, regardless of the input size n, the space used by the counter is bounded by the constant 24.

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 - (x % 24) without the outer modulo operation. This fails when x % 24 = 0.

Wrong Implementation:

needed_remainder = 24 - (hour % 24)  # Bug: when hour % 24 = 0, this gives 24 instead of 0

Why it's wrong:

  • When hour = 24, we have hour % 24 = 0
  • The complement should be 0 (since 0 + 0 = 0, which is divisible by 24)
  • But 24 - 0 = 24, which is outside our valid remainder range [0, 23]
  • Looking up remainder_count[24] will always return 0, missing valid pairs

Correct Implementation:

needed_remainder = (24 - (hour % 24)) % 24  # Correctly handles all cases

2. Counting Pairs in Wrong Order

The Pitfall: Updating the counter before checking for pairs, which leads to self-pairing when an element could pair with itself.

Wrong Implementation:

for hour in hours:
    remainder_count[hour % 24] += 1  # Updated BEFORE checking
    total_pairs += remainder_count[(24 - (hour % 24)) % 24]

Why it's wrong:

  • If we have an hour value like 12, it has remainder 12
  • Its complement is also 12 (since 12 + 12 = 24)
  • By updating the counter first, we incorrectly count this element as pairing with itself
  • This violates the constraint that we need distinct indices i < j

Correct Implementation:

for hour in hours:
    total_pairs += remainder_count[(24 - (hour % 24)) % 24]  # Check FIRST
    remainder_count[hour % 24] += 1  # Update AFTER

3. Using Regular Dictionary Instead of Counter

The Pitfall: Using a regular dictionary and forgetting to handle missing keys.

Wrong Implementation:

remainder_count = {}
for hour in hours:
    needed = (24 - (hour % 24)) % 24
    total_pairs += remainder_count[needed]  # KeyError if 'needed' not in dict

Why it's wrong:

  • Regular dictionaries raise KeyError when accessing non-existent keys
  • The first element will always cause an error since the dictionary is empty

Correct Implementation:

# Option 1: Use Counter (recommended)
remainder_count = Counter()

# Option 2: Use dict.get() with default value
remainder_count = {}
total_pairs += remainder_count.get(needed, 0)

# Option 3: Check key existence
if needed in remainder_count:
    total_pairs += remainder_count[needed]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More