Count Nice Pairs in an Array: Hash Map with Algebraic Trick
You are given an array of non-negative integers nums. A pair (i, j) where i < j is called a "nice pair" if nums[i] + rev(nums[j]) == nums[j] + rev(nums[i]), where rev(x) is the number obtained by reversing the digits of x. Return the number of nice pairs modulo 10^9 + 7.
This is LeetCode 1814: Count Nice Pairs in an Array, and it is one of the cleanest examples of how a bit of algebra can turn a brute-force O(n^2) problem into an O(n) hash map solution.
Why this problem matters
At first glance, checking every pair seems unavoidable. You have a condition involving two indices, both raw values and their reverses. The brute-force approach tries all pairs, which is too slow for arrays up to 10^5 elements.
But the equation hides a simplification. Once you see it, the problem collapses into "count how many elements share the same key," which is a pattern you have seen in problems like Group Anagrams and Two Sum. Recognizing when an equation can be rearranged to isolate each index on one side is a skill that shows up across many LeetCode problems involving pairs.
The key insight
The original condition is:
nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])
Move terms involving j to the right and terms involving i to the left:
nums[i] - rev(nums[i]) == nums[j] - rev(nums[j])
Now each side depends on only one index. Define diff[i] = nums[i] - rev(nums[i]). Two elements form a nice pair if and only if they have the same diff value. The problem reduces to: group elements by their diff, then count pairs within each group.
For a group of size c, the number of pairs is c * (c - 1) / 2. Or even simpler: as you iterate, when you see a diff that already appeared c times, that element forms c new pairs with all previously seen elements sharing that diff.
The solution
def count_nice_pairs(nums: list[int]) -> int:
MOD = 10**9 + 7
count = {}
result = 0
for num in nums:
diff = num - int(str(num)[::-1])
result = (result + count.get(diff, 0)) % MOD
count[diff] = count.get(diff, 0) + 1
return result
Let's walk through what each piece does.
The diff computation reverses the digits of num by converting to a string, reversing it, and converting back. Then it subtracts the reversed value from the original. This is the algebraic rearrangement in action: instead of comparing pairs, you compute a single key for each element.
The count dictionary tracks how many elements with each diff value you have seen so far. When you encounter a new element with diff = 18 and the map already holds count[18] = 3, that means three previous elements had the same diff. The current element forms a nice pair with each of them, so you add 3 to the result.
After adding to the result, you increment count[diff] so future elements can pair with this one too. This "count as you go" approach avoids a separate pass to compute c * (c - 1) / 2 for each group.
The modulo operation keeps the result within bounds since the answer can be extremely large for arrays with many duplicate diffs.
The "count as you go" trick works because when the k-th element with a given diff arrives, it pairs with all k-1 elements that came before it. Summing 0 + 1 + 2 + ... + (c-1) gives you c*(c-1)/2, the exact number of pairs for a group of size c. You get the same answer without needing a second pass.
Visual walkthrough
Let's trace through nums = [42, 11, 1, 97] step by step. Watch how the hash map accumulates counts and pairs are tallied on the fly.
Step 1: Compute diff = nums[i] - rev(nums[i]) for each element
This transforms the original condition into a simple grouping problem. Elements with the same diff value form nice pairs with each other.
Step 2: Group by diff using a hash map
Walk through the transformed array and count how many times each diff value appears.
Step 3: Count pairs while iterating
When you encounter a diff that is already in the map with count c, that means c elements before this one had the same diff. Each of them forms a nice pair with the current element, so add c to the result.
Step 4: Return result mod 10^9 + 7
The problem asks for the answer modulo 10^9 + 7 since counts can get very large. Apply the modulo at each addition step to prevent overflow.
The final answer is 2: pair (42, 97) with shared diff 18, and pair (11, 1) with shared diff 0.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Brute force (check all pairs) | O(n^2) | O(1) |
| Hash map with algebraic trick | O(n) | O(n) |
Time is O(n) where n is the length of the array. You iterate through the array once. For each element, you compute the reverse (O(d) where d is the number of digits, bounded by a small constant for 32-bit integers) and do a constant-time hash map lookup and update.
Space is O(n) in the worst case. If every element has a unique diff, the hash map stores n entries. In practice, many elements may share diff values, so the map is often smaller.
The building blocks
1. Algebraic rearrangement to isolate indices
The pattern of transforming f(i, j) == g(i, j) into h(i) == h(j):
diff = num - int(str(num)[::-1])
Whenever a pair condition involves both indices mixed together, try moving all terms for one index to one side. If you can express the condition as "some function of i equals the same function of j," you have reduced a pair-matching problem to a grouping problem. This technique appears in Two Sum (where nums[i] + nums[j] = target becomes "look up target - nums[i]"), Group Anagrams (where two strings are anagrams if they share the same sorted key), and many other problems.
2. Counting pairs from group sizes
The pattern of using a hash map to count elements sharing a key, then deriving pair counts:
result = (result + count.get(diff, 0)) % MOD
count[diff] = count.get(diff, 0) + 1
When you need to count pairs (i, j) with i < j sharing some property, maintain a running count. Each time you process an element, the current count tells you how many valid partners exist to the left. This avoids the c * (c - 1) / 2 formula entirely and handles the modular arithmetic naturally since you apply mod at each step.
Edge cases
Before submitting, think through these scenarios:
- All elements are palindromes (like
[11, 22, 33, 121]): every element hasdiff = 0, so the answer isn * (n - 1) / 2 mod 10^9 + 7. Make sure your modular arithmetic handles large counts. - All elements are unique diffs: no two elements share a diff, so the answer is 0. The hash map stores n entries but never adds to the result.
- Single-digit numbers:
rev(x) == xfor any single digit, sodiff = 0for all of them. Same as the palindrome case. - Large array with repeated values: if every element is the same number (like
[42, 42, 42, ...]), they all share the same diff. The pair count grows quadratically, so the modulo is essential.
From understanding to recall
You have seen how algebraic rearrangement transforms a pair condition into a grouping problem, and how a hash map counts pairs in a single pass. The logic is clean and the code is short. But reproducing it under pressure is a different challenge.
The part people stumble on is the rearrangement itself. During an interview, staring at nums[i] + rev(nums[j]) == nums[j] + rev(nums[i]) and realizing you should subtract rev(nums[i]) from both sides takes confidence that comes from practice. The counting trick of "add the current count, then increment" is another detail that is easy to understand but easy to mix up when you are nervous.
Spaced repetition is how you make these patterns automatic. You practice the algebraic rearrangement, the hash map counting loop, and the modular arithmetic from memory at increasing intervals. After a few rounds, you see "pair condition with a hidden symmetry" in a problem statement and the template flows out without hesitation.
Related posts
- Group Anagrams - Another hash map grouping problem where you compute a canonical key for each element and group by that key
- Two Sum - The foundational "find pairs with a property" problem that teaches hash map lookups for complements
- Contains Duplicate - The simplest form of "do any two elements share a property?" using a hash set
CodeBricks breaks Count Nice Pairs into its algebraic rearrangement and hash map counting building blocks, then drills them independently with spaced repetition. You type each piece from scratch until the pattern is automatic. When a pair-counting problem shows up in your interview, you do not think about it. You just write it.