1010. Pairs of Songs With Total Durations Divisible by 60
Problem Description
You have a playlist of songs where each song has a specific duration in seconds. The task is to find how many pairs of songs can be combined such that their total duration is exactly divisible by 60 seconds.
Given an array time
where time[i]
represents the duration of the i-th
song in seconds, you need to count all valid pairs (i, j)
where:
i < j
(to avoid counting the same pair twice and pairing a song with itself)(time[i] + time[j]) % 60 == 0
(the sum of their durations is divisible by 60)
For example, if you have songs with durations [30, 20, 150, 100, 40]
:
- Songs at indices 0 and 2:
30 + 150 = 180
, which is divisible by 60 ✓ - Songs at indices 1 and 3:
20 + 100 = 120
, which is divisible by 60 ✓ - Songs at indices 1 and 4:
20 + 40 = 60
, which is divisible by 60 ✓
The answer would be 3 pairs.
The solution uses modular arithmetic to optimize the counting. Since we only care about divisibility by 60, we can work with remainders when dividing by 60. Two numbers sum to a multiple of 60 if their remainders sum to either 0 or 60. The code counts frequencies of each remainder value and calculates valid pairs based on complementary remainders.
Intuition
The key insight is recognizing that we don't need to check every possible pair of songs, which would take O(n²)
time. Instead, we can use the properties of modular arithmetic to solve this more efficiently.
When we want to check if two numbers sum to a multiple of 60, we only care about their remainders when divided by 60. For example, 150
and 30
have the same effect as 90
and 30
when checking divisibility by 60, because 150 % 60 = 30
and 90 % 60 = 30
.
This leads us to think: for a song with duration that gives remainder r
when divided by 60, what remainder should its pair have? If the first song has remainder r
, then the second song must have remainder 60 - r
for their sum to be divisible by 60. For instance:
- A song with remainder
20
pairs with songs having remainder40
(since20 + 40 = 60
) - A song with remainder
15
pairs with songs having remainder45
(since15 + 45 = 60
)
There are two special cases to handle:
- When remainder is
0
: These songs pair with other songs that also have remainder0
(since0 + 0 = 0
, which is divisible by 60) - When remainder is
30
: These songs pair with other songs that also have remainder30
(since30 + 30 = 60
)
For these special cases, we're choosing 2 songs from the same group, so we use the combination formula n * (n - 1) / 2
to count pairs without repetition.
By counting the frequency of each remainder value from 0
to 59
, we can quickly calculate the total number of valid pairs by multiplying the counts of complementary remainders together.
Solution Approach
The solution uses a hash map (Counter) to track the frequency of each remainder value, then calculates the number of valid pairs based on these frequencies.
Step 1: Calculate remainders and count frequencies
cnt = Counter(t % 60 for t in time)
We iterate through all songs and calculate t % 60
for each duration t
. The Counter creates a frequency map where the key is the remainder (0-59) and the value is how many songs have that remainder.
Step 2: Count pairs with complementary remainders
ans = sum(cnt[x] * cnt[60 - x] for x in range(1, 30))
For remainders from 1 to 29, we pair each remainder x
with its complement 60 - x
. For example:
- Remainder 1 pairs with remainder 59
- Remainder 2 pairs with remainder 58
- Remainder 29 pairs with remainder 31
The number of pairs is cnt[x] * cnt[60 - x]
because each song with remainder x
can pair with any song having remainder 60 - x
.
We only iterate from 1 to 29 to avoid counting each pair twice (since remainder 1 with 59 is the same as remainder 59 with 1).
Step 3: Handle special case for remainder 0
ans += cnt[0] * (cnt[0] - 1) // 2
Songs with remainder 0 pair with other songs having remainder 0. Since we're selecting 2 songs from the same group of cnt[0]
songs, we use the combination formula C(n, 2) = n * (n - 1) / 2
.
Step 4: Handle special case for remainder 30
ans += cnt[30] * (cnt[30] - 1) // 2
Similarly, songs with remainder 30 pair with other songs having remainder 30 (since 30 + 30 = 60
). We apply the same combination formula.
The time complexity is O(n)
where n is the number of songs, and space complexity is O(1)
since we store at most 60 different remainder values.
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 a small example with time = [30, 20, 150, 100, 40]
.
Step 1: Calculate remainders and build frequency map
For each song duration, we calculate duration % 60
:
30 % 60 = 30
20 % 60 = 20
150 % 60 = 30
100 % 60 = 40
40 % 60 = 40
Our frequency counter becomes:
cnt[20] = 1
(one song with remainder 20)cnt[30] = 2
(two songs with remainder 30)cnt[40] = 2
(two songs with remainder 40)- All other remainders have count 0
Step 2: Count pairs with complementary remainders (1 to 29)
We iterate through remainders 1 to 29 and multiply counts with their complements:
- For
x = 20
: complement is60 - 20 = 40
- Pairs =
cnt[20] * cnt[40] = 1 * 2 = 2
- This accounts for songs at indices (1,3) and (1,4)
- Pairs =
- For all other values 1-29: either
cnt[x] = 0
orcnt[60-x] = 0
, contributing 0 pairs
Running total after this step: ans = 2
Step 3: Handle remainder 0
cnt[0] = 0
, so this contributes 0 * (0 - 1) / 2 = 0
pairs.
Running total: ans = 2
Step 4: Handle remainder 30
cnt[30] = 2
, so songs with remainder 30 can pair with each other.
- Pairs =
2 * (2 - 1) / 2 = 1
- This accounts for the pair at indices (0,2)
Final total: ans = 2 + 1 = 3
The three valid pairs are:
- Indices (0,2):
30 + 150 = 180
(divisible by 60) - Indices (1,3):
20 + 100 = 120
(divisible by 60) - Indices (1,4):
20 + 40 = 60
(divisible by 60)
Solution Implementation
1class Solution:
2 def numPairsDivisibleBy60(self, time: List[int]) -> int:
3 # Count frequency of each remainder when divided by 60
4 remainder_count = Counter(t % 60 for t in time)
5
6 # Initialize result counter
7 result = 0
8
9 # For remainders 1 to 29, pair with their complement (60 - remainder)
10 # Each pair sums to 60, making them divisible by 60
11 for remainder in range(1, 30):
12 complement = 60 - remainder
13 result += remainder_count[remainder] * remainder_count[complement]
14
15 # Handle special case: remainder 0
16 # Songs with remainder 0 can pair with each other
17 # Use combination formula: n * (n - 1) / 2
18 result += remainder_count[0] * (remainder_count[0] - 1) // 2
19
20 # Handle special case: remainder 30
21 # Songs with remainder 30 can pair with each other (30 + 30 = 60)
22 # Use combination formula: n * (n - 1) / 2
23 result += remainder_count[30] * (remainder_count[30] - 1) // 2
24
25 return result
26
1class Solution {
2 public int numPairsDivisibleBy60(int[] time) {
3 // Array to count frequencies of remainders when divided by 60
4 int[] remainderCount = new int[60];
5
6 // Count the frequency of each remainder (0 to 59)
7 for (int duration : time) {
8 remainderCount[duration % 60]++;
9 }
10
11 // Initialize result counter for valid pairs
12 int pairCount = 0;
13
14 // Count pairs where remainders sum to 60
15 // For remainders 1 to 29, pair with their complement (60 - remainder)
16 for (int remainder = 1; remainder < 30; remainder++) {
17 pairCount += remainderCount[remainder] * remainderCount[60 - remainder];
18 }
19
20 // Special case: pairs where both songs have remainder 0 (divisible by 60)
21 // Use combination formula: n * (n - 1) / 2 for choosing 2 from n elements
22 pairCount += (long) remainderCount[0] * (remainderCount[0] - 1) / 2;
23
24 // Special case: pairs where both songs have remainder 30
25 // These pairs sum to 60, use same combination formula
26 pairCount += (long) remainderCount[30] * (remainderCount[30] - 1) / 2;
27
28 return pairCount;
29 }
30}
31
1class Solution {
2public:
3 int numPairsDivisibleBy60(vector<int>& time) {
4 // Array to count occurrences of each remainder when divided by 60
5 int remainderCount[60]{};
6
7 // Count the frequency of each remainder (0 to 59)
8 for (int& duration : time) {
9 ++remainderCount[duration % 60];
10 }
11
12 int totalPairs = 0;
13
14 // For remainders 1 to 29, pair with their complement (60 - remainder)
15 // These form pairs that sum to 60
16 for (int remainder = 1; remainder < 30; ++remainder) {
17 totalPairs += remainderCount[remainder] * remainderCount[60 - remainder];
18 }
19
20 // Special case: remainder 0 pairs with itself (0 + 0 = 0, divisible by 60)
21 // Use combination formula C(n, 2) = n * (n - 1) / 2
22 totalPairs += 1LL * remainderCount[0] * (remainderCount[0] - 1) / 2;
23
24 // Special case: remainder 30 pairs with itself (30 + 30 = 60, divisible by 60)
25 // Use combination formula C(n, 2) = n * (n - 1) / 2
26 totalPairs += 1LL * remainderCount[30] * (remainderCount[30] - 1) / 2;
27
28 return totalPairs;
29 }
30};
31
1/**
2 * Counts the number of pairs of songs with total duration divisible by 60
3 * @param time - Array of song durations in seconds
4 * @returns Number of pairs (i, j) where i < j and (time[i] + time[j]) % 60 == 0
5 */
6function numPairsDivisibleBy60(time: number[]): number {
7 // Create frequency array to count remainders when divided by 60
8 // Index represents remainder (0-59), value represents count
9 const remainderCount: number[] = new Array(60).fill(0);
10
11 // Count frequency of each remainder
12 for (const duration of time) {
13 remainderCount[duration % 60]++;
14 }
15
16 let pairCount: number = 0;
17
18 // Count pairs where remainders sum to 60
19 // For remainder x, we need remainder (60 - x) to form a valid pair
20 for (let remainder = 1; remainder < 30; remainder++) {
21 pairCount += remainderCount[remainder] * remainderCount[60 - remainder];
22 }
23
24 // Handle special case: remainder 0 pairs with remainder 0 (sum divisible by 60)
25 // Use combination formula: n * (n - 1) / 2 for choosing 2 from n elements
26 pairCount += (remainderCount[0] * (remainderCount[0] - 1)) / 2;
27
28 // Handle special case: remainder 30 pairs with remainder 30 (30 + 30 = 60)
29 // Use combination formula: n * (n - 1) / 2 for choosing 2 from n elements
30 pairCount += (remainderCount[30] * (remainderCount[30] - 1)) / 2;
31
32 return pairCount;
33}
34
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two main parts:
- Creating a Counter object from the list of remainders when each time value is divided by 60 - this requires iterating through all
n
elements in thetime
list, takingO(n)
time. - Computing the answer by iterating through a fixed range from 1 to 29 (constant time
O(1)
), plus two additional constant-time operations for special cases at indices 0 and 30.
Since the iteration in step 2 is over a constant range (29 iterations), it contributes O(1)
to the overall complexity. Therefore, the total time complexity is O(n) + O(1) = O(n)
.
Space Complexity: O(1)
The Counter object cnt
stores the frequency of remainders when dividing by 60. Since the remainder of any number divided by 60 can only be in the range [0, 59]
, the Counter will have at most 60 unique keys. This means the space used by the Counter is bounded by a constant (60), regardless of the input size n
. Therefore, the space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Double Counting Pairs
Pitfall: A common mistake is iterating through all remainders from 0 to 59 and counting pairs, which results in counting each pair twice.
# WRONG: This counts each pair twice
result = 0
for remainder in range(60):
complement = (60 - remainder) % 60
result += remainder_count[remainder] * remainder_count[complement]
return result // 2 # Dividing by 2 doesn't fix all cases
Why it's wrong: When you process remainder 1, you count pairs with remainder 59. Later, when processing remainder 59, you count the same pairs again. Additionally, the special cases (remainder 0 and 30) would be incorrectly handled.
Solution: Only iterate through remainders 1 to 29, and handle special cases separately:
# CORRECT: Process each unique pair once
for remainder in range(1, 30):
complement = 60 - remainder
result += remainder_count[remainder] * remainder_count[complement]
2. Incorrect Handling of Remainder 0
Pitfall: Treating remainder 0 like other remainders and trying to find its complement.
# WRONG: Trying to pair remainder 0 with remainder 60 complement = 60 - 0 # This gives 60, which doesn't exist as a remainder result += remainder_count[0] * remainder_count[60] # KeyError or 0
Why it's wrong: The remainder when dividing by 60 can only be 0-59. There's no remainder 60. Songs with remainder 0 should pair with other songs having remainder 0.
Solution: Use combination formula for self-pairing:
# CORRECT: Songs with remainder 0 pair with each other result += remainder_count[0] * (remainder_count[0] - 1) // 2
3. Forgetting the Remainder 30 Special Case
Pitfall: Not recognizing that remainder 30 is self-complementary (30 + 30 = 60).
# WRONG: Only handling remainder 0 as special case
for remainder in range(1, 30):
result += remainder_count[remainder] * remainder_count[60 - remainder]
result += remainder_count[0] * (remainder_count[0] - 1) // 2
# Missing remainder 30 handling!
Why it's wrong: Remainder 30 pairs with itself to make 60, similar to how remainder 0 pairs with itself to make 0 (which is divisible by 60).
Solution: Apply the same combination formula to remainder 30:
# CORRECT: Handle both special cases result += remainder_count[0] * (remainder_count[0] - 1) // 2 result += remainder_count[30] * (remainder_count[30] - 1) // 2
4. Using Wrong Combination Formula
Pitfall: Using multiplication instead of combination for self-pairing cases.
# WRONG: This counts pairing a song with itself result += remainder_count[0] * remainder_count[0]
Why it's wrong: This would count invalid pairs like (i, i) and count valid pairs twice.
Solution: Use the proper combination formula C(n, 2):
# CORRECT: Choose 2 songs from n songs result += remainder_count[0] * (remainder_count[0] - 1) // 2
Which of these properties could exist for a graph but not a tree?
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!