A clear explanation of counting song pairs whose total duration is divisible by 60 using remainder frequency counting.
Problem Restatement
We are given a list of song durations time.
We want to count pairs of songs (i, j) where i < j and:
(time[i] + time[j]) % 60 == 0We need to return the number of such pairs.
The official constraints state that 1 <= time.length <= 6 * 10^4 and 1 <= time[i] <= 500.
Input and Output
| Item | Meaning |
|---|---|
| Input | Array time of song durations |
| Output | Number of pairs with total duration divisible by 60 |
Function shape:
def numPairsDivisibleBy60(time: list[int]) -> int:
...Examples
Example 1:
time = [30, 20, 150, 100, 40]The valid pairs are:
| Indices | Durations | Sum | Divisible by 60? |
|---|---|---|---|
(0, 2) | 30 + 150 | 180 | Yes |
(1, 3) | 20 + 100 | 120 | Yes |
(1, 4) | 20 + 40 | 60 | Yes |
So the answer is:
3Example 2:
time = [60, 60, 60]All pairs: (0,1), (0,2), (1,2). Each sum is 120, divisible by 60.
Answer:
3Key Insight
This is analogous to Two Sum, but with modular arithmetic.
For two durations to sum to a multiple of 60, their remainders mod 60 must complement each other:
(r1 + r2) % 60 == 0This means r2 == (60 - r1) % 60.
Count frequencies of remainders, then for each duration, look up how many previous durations have the complementary remainder.
Algorithm
- Create a
countarray of size60initialized to0. - Initialize
pairs = 0. - For each
tintime:- Compute
r = t % 60. - Add
count[(60 - r) % 60]topairs. - Increment
count[r].
- Compute
- Return
pairs.
Correctness
For each song, we count how many previously seen songs have a complementary remainder. This correctly counts unordered pairs because we always look at songs to the left (earlier index) before updating the count.
The modulo (60 - r) % 60 handles the edge case where r = 0: the complement is 0 (not 60), and (60 - 0) % 60 = 0.
Edge Cases
- Duplicates usually matter; store counts when a set would lose necessary multiplicity.
- Update the frequency structure in the same order the invariant assumes.
- Check empty or one-element inputs if the problem allows them.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | One pass through the array |
| Space | O(1) | Fixed-size count array of 60 |
Common Pitfalls
- Do not optimize away the invariant; the code should still make it clear what condition is being maintained.
- Prefer problem-specific names over one-letter variables once the logic becomes stateful.
Implementation
class Solution:
def numPairsDivisibleBy60(self, time: list[int]) -> int:
count = [0] * 60
pairs = 0
for t in time:
r = t % 60
pairs += count[(60 - r) % 60]
count[r] += 1
return pairsCode Explanation
For each song duration t, compute its remainder:
r = t % 60Check how many previous songs have the complementary remainder:
pairs += count[(60 - r) % 60]The % 60 handles the case r = 0 where the complement is 0, not 60.
Update the count for the current remainder:
count[r] += 1Testing
def run_tests():
s = Solution()
assert s.numPairsDivisibleBy60([30, 20, 150, 100, 40]) == 3
assert s.numPairsDivisibleBy60([60, 60, 60]) == 3
assert s.numPairsDivisibleBy60([30, 30]) == 1
assert s.numPairsDivisibleBy60([10, 20, 30]) == 0
assert s.numPairsDivisibleBy60([60]) == 0
print("all tests passed")
run_tests()| Test | Expected | Why |
|---|---|---|
| Standard example | 3 | Three complementary pairs |
| Three 60s | 3 | All three pairs sum to 120 |
| Two 30s | 1 | 30 + 30 = 60 |
| No pairs | 0 | No complementary remainders |
| Single song | 0 | No pairs possible |