1814. Count Nice Pairs in an Array
Problem Description
You have an array nums
containing non-negative integers. For any non-negative integer x
, we define rev(x)
as the number obtained by reversing its digits. For example, rev(123) = 321
and rev(120) = 21
(leading zeros are removed).
A pair of indices (i, j)
is called a "nice pair" if it meets these two conditions:
- The indices satisfy
0 <= i < j < nums.length
(i comes before j) - The equation
nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])
holds true
Your task is to count how many nice pairs exist in the array. Since the answer might be very large, return the result modulo 10^9 + 7
.
The key insight for solving this problem involves transforming the equation. When nums[i] + rev(nums[j]) = nums[j] + rev(nums[i])
, we can rearrange it to get nums[i] - rev(nums[i]) = nums[j] - rev(nums[j])
. This means two indices form a nice pair if the difference between each number and its reverse is the same.
The solution uses a hash table to count how many numbers have the same difference value (x - rev(x))
. For each group of numbers with the same difference, we can form n * (n-1) / 2
pairs where n
is the count of numbers in that group. This formula calculates the number of ways to choose 2 items from n items, which gives us all possible pairs.
Intuition
The original condition nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])
looks complex at first glance. The breakthrough comes when we realize we can rearrange this equation algebraically.
By moving terms around, we get:
nums[i] + rev(nums[j]) = nums[j] + rev(nums[i])
nums[i] - nums[j] = rev(nums[i]) - rev(nums[j])
nums[i] - rev(nums[i]) = nums[j] - rev(nums[j])
This transformation reveals something crucial: for any pair (i, j)
to be nice, the value nums[i] - rev(nums[i])
must equal nums[j] - rev(nums[j])
. In other words, both indices must produce the same difference when we subtract their reverse from the original number.
This observation transforms our problem from checking all possible pairs (which would be O(n²)) to a counting problem. We can compute x - rev(x)
for each element in the array just once. If multiple elements produce the same difference value, any two of them can form a nice pair.
For example, if three numbers all have the same difference value, we can form 3 * 2 / 2 = 3
nice pairs from them. This is the combination formula C(n, 2) = n * (n-1) / 2
, which counts the number of ways to choose 2 items from n items.
The hash table approach naturally emerges from this insight: we count how many times each difference value appears, then for each group with count v
, we add v * (v-1) / 2
to our answer. This efficiently counts all nice pairs without having to check every possible pair individually.
Learn more about Math patterns.
Solution Approach
The implementation follows the mathematical transformation we discovered: for a nice pair (i, j)
, the condition nums[i] - rev(nums[i]) = nums[j] - rev(nums[j])
must hold.
First, we implement a helper function rev(x)
to reverse a number's digits:
- Initialize result
y = 0
- While
x
has digits remaining:- Extract the last digit using
x % 10
- Append it to
y
by multiplyingy
by 10 and adding the digit - Remove the last digit from
x
using integer divisionx //= 10
- Extract the last digit using
- Return the reversed number
y
Next, we transform each element in the array:
- For each number
x
innums
, calculatex - rev(x)
- Use Python's
Counter
to count the frequency of each difference value - This creates a hash table where keys are difference values and values are their counts
Finally, we count the nice pairs:
- For each unique difference value with count
v
in our hash table:- Calculate the number of pairs using the combination formula
v * (v - 1) // 2
- This counts all possible ways to choose 2 elements from
v
elements
- Calculate the number of pairs using the combination formula
- Sum up all these pair counts
- Apply modulo
10^9 + 7
to the final result to prevent overflow
The time complexity is O(n * k) where n is the length of the array and k is the average number of digits in the numbers (for the reversal operation). The space complexity is O(n) for the hash table storage.
This approach efficiently reduces what would be an O(n²) brute force solution to a linear time solution by recognizing the mathematical pattern and using appropriate data structures.
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 nums = [42, 11, 1, 97]
.
Step 1: Calculate reversed numbers
rev(42) = 24
(reverse digits of 42)rev(11) = 11
(palindrome stays the same)rev(1) = 1
(single digit stays the same)rev(97) = 79
(reverse digits of 97)
Step 2: Calculate differences for each number
For each number, compute x - rev(x)
:
42 - rev(42) = 42 - 24 = 18
11 - rev(11) = 11 - 11 = 0
1 - rev(1) = 1 - 1 = 0
97 - rev(97) = 97 - 79 = 18
Step 3: Count frequencies of each difference Using a hash table to count occurrences:
- Difference
18
: appears 2 times (from indices 0 and 3) - Difference
0
: appears 2 times (from indices 1 and 2)
Step 4: Calculate nice pairs for each group
For each difference value with count v
, we can form v * (v-1) / 2
pairs:
- Difference
18
with count 2:2 * (2-1) / 2 = 1
pair- This represents the pair (0, 3) since
nums[0]
andnums[3]
both have difference 18
- This represents the pair (0, 3) since
- Difference
0
with count 2:2 * (2-1) / 2 = 1
pair- This represents the pair (1, 2) since
nums[1]
andnums[2]
both have difference 0
- This represents the pair (1, 2) since
Step 5: Sum up total pairs Total nice pairs = 1 + 1 = 2
Verification: Let's verify our nice pairs satisfy the original condition:
- Pair (0, 3):
nums[0] + rev(nums[3]) = 42 + 79 = 121
andnums[3] + rev(nums[0]) = 97 + 24 = 121
✓ - Pair (1, 2):
nums[1] + rev(nums[2]) = 11 + 1 = 12
andnums[2] + rev(nums[1]) = 1 + 11 = 12
✓
The answer is 2 nice pairs.
Solution Implementation
1class Solution:
2 def countNicePairs(self, nums: List[int]) -> int:
3 def reverse_number(num):
4 """
5 Reverse the digits of a given number.
6 For example: 123 -> 321, 1200 -> 21
7 """
8 reversed_num = 0
9 while num:
10 reversed_num = reversed_num * 10 + num % 10
11 num //= 10
12 return reversed_num
13
14 # Transform the problem: nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])
15 # Can be rewritten as: nums[i] - rev(nums[i]) == nums[j] - rev(nums[j])
16 # Count frequency of each (num - reverse(num)) value
17 difference_counter = Counter(num - reverse_number(num) for num in nums)
18
19 # Define modulo for the result as per problem constraints
20 MOD = 10**9 + 7
21
22 # For each group with the same difference value, calculate number of pairs
23 # Number of pairs from n elements is n * (n - 1) / 2 (combination formula)
24 total_pairs = sum(count * (count - 1) // 2 for count in difference_counter.values())
25
26 return total_pairs % MOD
27
1class Solution {
2 /**
3 * Counts the number of nice pairs in the array.
4 * A nice pair (i, j) where i < j satisfies: nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])
5 * This can be rearranged to: nums[i] - rev(nums[i]) == nums[j] - rev(nums[j])
6 *
7 * @param nums the input array of positive integers
8 * @return the count of nice pairs modulo 10^9 + 7
9 */
10 public int countNicePairs(int[] nums) {
11 // Map to store frequency of (num - reverse(num)) values
12 Map<Integer, Integer> frequencyMap = new HashMap<>();
13
14 // Calculate num - reverse(num) for each element and count frequencies
15 for (int num : nums) {
16 int difference = num - rev(num);
17 frequencyMap.merge(difference, 1, Integer::sum);
18 }
19
20 // Modulo value as per problem requirement
21 final int MOD = (int) 1e9 + 7;
22
23 // Calculate total nice pairs using combination formula C(n, 2) = n * (n - 1) / 2
24 long totalPairs = 0;
25 for (int frequency : frequencyMap.values()) {
26 // For each group with same difference value, calculate pairs
27 totalPairs = (totalPairs + (long) frequency * (frequency - 1) / 2) % MOD;
28 }
29
30 return (int) totalPairs;
31 }
32
33 /**
34 * Reverses the digits of a given integer.
35 * For example: 123 becomes 321, 1000 becomes 1
36 *
37 * @param x the integer to reverse
38 * @return the reversed integer
39 */
40 private int rev(int x) {
41 int reversed = 0;
42
43 // Extract digits from right to left and build reversed number
44 while (x > 0) {
45 reversed = reversed * 10 + x % 10;
46 x /= 10;
47 }
48
49 return reversed;
50 }
51}
52
1class Solution {
2public:
3 int countNicePairs(vector<int>& nums) {
4 // Lambda function to reverse the digits of a number
5 auto reverseNumber = [](int number) {
6 int reversed = 0;
7 while (number > 0) {
8 reversed = reversed * 10 + number % 10;
9 number /= 10;
10 }
11 return reversed;
12 };
13
14 // Map to store frequency of (num - reverse(num)) values
15 // Key insight: nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])
16 // can be rewritten as: nums[i] - rev(nums[i]) == nums[j] - rev(nums[j])
17 unordered_map<int, int> frequencyMap;
18
19 // Calculate difference between each number and its reverse
20 for (int& number : nums) {
21 int difference = number - reverseNumber(number);
22 frequencyMap[difference]++;
23 }
24
25 // Count total number of nice pairs
26 long long totalPairs = 0;
27 const int MOD = 1e9 + 7;
28
29 // For each group with same difference value, calculate combinations C(n, 2)
30 // Number of pairs from n elements = n * (n - 1) / 2
31 for (auto& [key, frequency] : frequencyMap) {
32 totalPairs = (totalPairs + 1LL * frequency * (frequency - 1) / 2) % MOD;
33 }
34
35 return totalPairs;
36 }
37};
38
1/**
2 * Counts the number of nice pairs in the array.
3 * A nice pair (i, j) where i < j satisfies: nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])
4 * This can be rewritten as: nums[i] - rev(nums[i]) == nums[j] - rev(nums[j])
5 *
6 * @param nums - Array of non-negative integers
7 * @returns Number of nice pairs modulo 10^9 + 7
8 */
9function countNicePairs(nums: number[]): number {
10 /**
11 * Reverses the digits of a number
12 * Example: 123 -> 321, 100 -> 1
13 *
14 * @param x - The number to reverse
15 * @returns The reversed number
16 */
17 const reverseNumber = (x: number): number => {
18 let reversed = 0;
19
20 while (x > 0) {
21 // Extract the last digit and append it to the reversed number
22 reversed = reversed * 10 + (x % 10);
23 // Remove the last digit from x
24 x = Math.floor(x / 10);
25 }
26
27 return reversed;
28 };
29
30 // Modulo value to prevent integer overflow
31 const MODULO = 10 ** 9 + 7;
32
33 // Map to store frequency of each difference value (num - reverse(num))
34 const differenceFrequencyMap = new Map<number, number>();
35
36 // Total count of nice pairs
37 let nicePairsCount = 0;
38
39 // Iterate through each number in the array
40 for (const currentNum of nums) {
41 // Calculate the difference: num - reverse(num)
42 const difference = currentNum - reverseNumber(currentNum);
43
44 // Add the count of previous numbers with the same difference
45 // These form nice pairs with the current number
46 nicePairsCount = (nicePairsCount + (differenceFrequencyMap.get(difference) ?? 0)) % MODULO;
47
48 // Update the frequency of the current difference
49 differenceFrequencyMap.set(difference, (differenceFrequencyMap.get(difference) ?? 0) + 1);
50 }
51
52 return nicePairsCount;
53}
54
Time and Space Complexity
Time Complexity: O(n × log M)
The time complexity consists of two main parts:
- The list comprehension iterates through all
n
elements innums
- For each element
x
, therev(x)
function is called, which takesO(log M)
time whereM
is the maximum value in the array (since the number of digits inx
is proportional tolog x
) - Computing
x - rev(x)
for each element takesO(1)
after reversing - Creating the Counter takes
O(n)
time - The final sum operation iterates through at most
n
unique values in the Counter and performsO(1)
arithmetic operations for each
Therefore, the dominant operation is the reversal calculation for each element, giving us O(n × log M)
.
Space Complexity: O(n)
The space complexity is determined by:
- The Counter object
cnt
which stores at mostn
key-value pairs (in the worst case where allx - rev(x)
values are unique) - The list comprehension creates a temporary list of size
n
before passing it to Counter - The
rev
function usesO(1)
auxiliary space for variablesy
andx
The dominant space usage is the Counter storage, resulting in O(n)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Pair Counting
Pitfall: When calculating count * (count - 1) // 2
, the multiplication count * (count - 1)
can cause integer overflow before the division, especially when count
is large. Although Python handles big integers automatically, in other languages or when dealing with very large counts, this intermediate result might exceed integer limits.
Solution: Apply modulo operation during the calculation rather than only at the end:
total_pairs = 0 for count in difference_counter.values(): total_pairs = (total_pairs + count * (count - 1) // 2) % MOD return total_pairs
2. Incorrect Reversal Implementation for Numbers Ending in Zeros
Pitfall: A common mistake is not handling trailing zeros correctly when reversing numbers. For example, reversing 1200 should give 21, not 0021. Some implementations might accidentally preserve leading zeros or handle them incorrectly.
Solution: The provided implementation correctly handles this by building the reversed number through arithmetic operations rather than string manipulation:
def reverse_number(num):
reversed_num = 0
while num:
reversed_num = reversed_num * 10 + num % 10
num //= 10
return reversed_num
3. Missing Edge Case: Single Element or No Valid Pairs
Pitfall: Not handling arrays with only one element or arrays where no nice pairs exist. The combination formula count * (count - 1) // 2
naturally handles these cases (returns 0 when count = 1), but explicit validation might be overlooked.
Solution: The current implementation handles this correctly through the combination formula. When count = 1
, the result is 1 * 0 // 2 = 0
, which is correct since you can't form a pair with a single element.
4. Using String Reversal Instead of Numerical Reversal
Pitfall: Using string manipulation to reverse numbers like int(str(num)[::-1])
can be problematic:
- It's generally slower than arithmetic operations
- It might not handle edge cases like single digit numbers or zeros correctly
- Leading zeros in the string representation could cause issues
Solution: Stick to arithmetic operations for reversal as shown in the provided solution, which is both more efficient and handles all edge cases correctly.
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!