3265. Count Almost Equal Pairs I
Problem Description
You are given an array nums
consisting of positive integers.
Two integers x
and y
are considered almost equal if they can become equal after performing the following operation at most once:
- Choose either
x
ory
- Swap any two digits within the chosen number
Your task is to count the number of pairs of indices (i, j)
where i < j
such that nums[i]
and nums[j]
are almost equal.
For example:
- Numbers
123
and321
are almost equal because swapping the first and last digits of123
gives321
- Numbers
123
and123
are almost equal because they're already equal (no swap needed) - Numbers
12
and21
are almost equal because swapping the two digits of either number makes them equal
Important notes:
- Leading zeros are allowed after performing a swap operation (e.g.,
100
can become001
by swapping) - The operation can be performed at most once, meaning you can also choose not to swap at all
- You need to count pairs where the first index is less than the second index (
i < j
)
The solution involves:
- Sorting the array first to handle cases where smaller numbers might be obtained through swapping
- For each number, generating all possible numbers that can be obtained by swapping any two digits (or keeping it unchanged)
- Using a counter to track previously seen numbers and checking how many of them match the current number's possible variations
- Accumulating the count of valid pairs
Intuition
The key insight is that for each number, we need to find all other numbers in the array that can become equal to it through at most one digit swap. A naive approach would be to compare every pair of numbers and check if they're almost equal, but this would be inefficient.
Instead, we can think about it differently: for each number, what are all the possible values it could transform into with at most one swap? If we generate all these possibilities and store them in a set, we can then check if any previously seen numbers match these possibilities.
Why do we need to sort the array first? Consider numbers like 100
and 1
. When we swap digits in 100
, we can get 001
which equals 1
. However, if we process 100
before 1
, we won't find 1
in our count of previously seen numbers, missing this valid pair. By sorting the array, we ensure smaller numbers are processed first, so when we encounter 100
, the number 1
has already been counted.
The approach becomes:
- For each number
x
, generate all possible numbers by swapping any two digits (including not swapping at all, which keeps the original number) - Check how many of these generated numbers we've already seen before in our traversal
- Add the current number to our count for future comparisons
This way, we avoid checking every pair explicitly. Instead, we leverage a hash table to track previously seen numbers and efficiently find matches. The time complexity improves because generating all swap variations for a single number is bounded by the number of digit pairs, which is at most O(dΒ²)
where d
is the number of digits, typically small.
The sorting step ensures we don't miss pairs where a larger number can transform into a smaller one through digit swapping.
Learn more about Sorting patterns.
Solution Approach
The implementation follows these steps:
-
Sort the array: We start by sorting
nums
in ascending order. This ensures that when we process a number, all smaller numbers that it could potentially match with have already been processed. -
Initialize data structures:
ans
: Counter for the number of valid pairscnt
: Adefaultdict(int)
that tracks how many times each number has been seen so far
-
Process each number: For each number
x
in the sorted array:a. Generate all possible variations: Create a set
vis
to store all numbers that can be obtained by swapping at most one pair of digits:- Start by adding the original number
x
tovis
(representing no swap) - Convert the number to a list of characters:
s = list(str(x))
- Use nested loops to try swapping every pair of digits:
for j in range(len(s)): for i in range(j): s[i], s[j] = s[j], s[i] # Swap digits at positions i and j vis.add(int("".join(s))) # Add the new number to vis s[i], s[j] = s[j], s[i] # Swap back to restore original
b. Count matches with previously seen numbers:
- For each number in
vis
, check how many times it has appeared before incnt
- Add the total count to
ans
:ans += sum(cnt[x] for x in vis)
c. Update the counter: Add the current number
x
tocnt
for future comparisons:cnt[x] += 1
- Start by adding the original number
-
Return the result: The final value of
ans
represents the total number of valid pairs.
Example walkthrough:
Consider nums = [1, 10, 100]
:
- Process
1
:vis = {1}
, no previous matches,cnt[1] = 1
- Process
10
:vis = {10, 1}
(swapping gives01
=1
), finds1
incnt
, adds 1 to answer,cnt[10] = 1
- Process
100
:vis = {100, 10, 1}
(various swaps), finds both1
and10
incnt
, adds 2 to answer - Final answer: 3 pairs
The time complexity is O(n log n + n Γ dΒ²)
where n
is the array length and d
is the maximum number of digits. The space complexity is O(n Γ dΒ²)
for storing all variations.
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 detailed example with nums = [13, 31, 103, 310]
:
Step 1: Sort the array
- After sorting:
nums = [13, 31, 103, 310]
Step 2: Process each number
Processing 13
:
- Convert to string:
"13"
- Generate all possible variations by swapping:
- No swap:
13
- Swap positions 0 and 1:
31
- No swap:
vis = {13, 31}
- Check counter
cnt
for matches: Both13
and31
have count 0 (not seen yet) - Add to answer:
ans = 0
- Update counter:
cnt[13] = 1
Processing 31
:
- Convert to string:
"31"
- Generate all possible variations:
- No swap:
31
- Swap positions 0 and 1:
13
- No swap:
vis = {31, 13}
- Check counter for matches:
cnt[31] = 0
,cnt[13] = 1
- Add to answer:
ans = 0 + 1 = 1
(found one13
) - Update counter:
cnt[31] = 1
Processing 103
:
- Convert to string:
"103"
- Generate all possible variations:
- No swap:
103
- Swap positions 0 and 1:
013
=13
- Swap positions 0 and 2:
301
- Swap positions 1 and 2:
130
- No swap:
vis = {103, 13, 301, 130}
- Check counter for matches:
cnt[103] = 0
,cnt[13] = 1
,cnt[301] = 0
,cnt[130] = 0
- Add to answer:
ans = 1 + 1 = 2
(found one13
) - Update counter:
cnt[103] = 1
Processing 310
:
- Convert to string:
"310"
- Generate all possible variations:
- No swap:
310
- Swap positions 0 and 1:
130
- Swap positions 0 and 2:
013
=13
- Swap positions 1 and 2:
301
- No swap:
vis = {310, 130, 13, 301}
- Check counter for matches:
cnt[310] = 0
,cnt[130] = 0
,cnt[13] = 1
,cnt[301] = 0
- Add to answer:
ans = 2 + 1 = 3
(found one13
) - Update counter:
cnt[310] = 1
Final Result: ans = 3
The three valid pairs are:
(0, 1)
:13
and31
- can swap digits of13
to get31
(0, 2)
:13
and103
- can swap first and second digits of103
to get013
=13
(0, 3)
:13
and310
- can swap first and third digits of310
to get013
=13
Solution Implementation
1from typing import List
2from collections import defaultdict
3
4class Solution:
5 def countPairs(self, nums: List[int]) -> int:
6 # Sort the array to process numbers in ascending order
7 nums.sort()
8
9 # Initialize the result counter
10 result = 0
11
12 # Dictionary to store frequency of each number seen so far
13 frequency = defaultdict(int)
14
15 # Process each number in the sorted array
16 for current_num in nums:
17 # Set to store all possible numbers after at most one swap
18 possible_values = {current_num}
19
20 # Convert number to list of digit characters for swapping
21 digits = list(str(current_num))
22
23 # Try all possible single swaps of digits
24 for j in range(len(digits)):
25 for i in range(j):
26 # Swap digits at positions i and j
27 digits[i], digits[j] = digits[j], digits[i]
28
29 # Add the resulting number to possible values
30 swapped_num = int("".join(digits))
31 possible_values.add(swapped_num)
32
33 # Swap back to restore original digit order
34 digits[i], digits[j] = digits[j], digits[i]
35
36 # Count pairs with all previously seen numbers that match any possible value
37 for value in possible_values:
38 result += frequency[value]
39
40 # Increment frequency of current number for future pairs
41 frequency[current_num] += 1
42
43 return result
44
1class Solution {
2 /**
3 * Counts the number of pairs (i, j) where i < j and nums[i] and nums[j] are almost equal.
4 * Two numbers are almost equal if they can be made equal by swapping at most one pair of digits.
5 *
6 * @param nums the input array of integers
7 * @return the count of almost equal pairs
8 */
9 public int countPairs(int[] nums) {
10 // Sort the array to ensure we process elements in order
11 Arrays.sort(nums);
12
13 int pairCount = 0;
14 // Map to store the frequency of each number processed so far
15 Map<Integer, Integer> frequencyMap = new HashMap<>();
16
17 // Process each number in the sorted array
18 for (int currentNumber : nums) {
19 // Set to store all possible numbers that can be formed by swapping digits
20 Set<Integer> possibleSwaps = new HashSet<>();
21
22 // Add the original number (no swap needed)
23 possibleSwaps.add(currentNumber);
24
25 // Convert number to character array for digit manipulation
26 char[] digits = String.valueOf(currentNumber).toCharArray();
27
28 // Generate all possible single-swap variations
29 for (int j = 0; j < digits.length; ++j) {
30 for (int i = 0; i < j; ++i) {
31 // Swap digits at positions i and j
32 swap(digits, i, j);
33
34 // Convert swapped digits back to integer and add to set
35 possibleSwaps.add(Integer.parseInt(String.valueOf(digits)));
36
37 // Swap back to restore original digit arrangement
38 swap(digits, i, j);
39 }
40 }
41
42 // Count pairs with previously processed numbers
43 for (int swappedNumber : possibleSwaps) {
44 // Add frequency of this swapped number from previously processed elements
45 pairCount += frequencyMap.getOrDefault(swappedNumber, 0);
46 }
47
48 // Update frequency map with current number
49 frequencyMap.merge(currentNumber, 1, Integer::sum);
50 }
51
52 return pairCount;
53 }
54
55 /**
56 * Helper method to swap two characters in a character array
57 *
58 * @param charArray the character array
59 * @param i first index to swap
60 * @param j second index to swap
61 */
62 private void swap(char[] charArray, int i, int j) {
63 char temp = charArray[i];
64 charArray[i] = charArray[j];
65 charArray[j] = temp;
66 }
67}
68
1class Solution {
2public:
3 int countPairs(vector<int>& nums) {
4 // Sort the array to process numbers in ascending order
5 sort(nums.begin(), nums.end());
6
7 int pairCount = 0;
8 // Map to store frequency of each number seen so far
9 unordered_map<int, int> frequencyMap;
10
11 for (int currentNum : nums) {
12 // Set to store all possible numbers that can be formed by swapping digits
13 unordered_set<int> swappedVariants = {currentNum};
14
15 // Convert current number to string for digit manipulation
16 string numStr = to_string(currentNum);
17
18 // Generate all possible numbers by swapping any two digits
19 for (int j = 0; j < numStr.length(); ++j) {
20 for (int i = 0; i < j; ++i) {
21 // Swap digits at positions i and j
22 swap(numStr[i], numStr[j]);
23 // Add the new number formed after swapping to the set
24 swappedVariants.insert(stoi(numStr));
25 // Swap back to restore original string
26 swap(numStr[i], numStr[j]);
27 }
28 }
29
30 // Count pairs: for each variant, add how many times it appeared before
31 for (int variant : swappedVariants) {
32 pairCount += frequencyMap[variant];
33 }
34
35 // Increment frequency of current number for future pairs
36 frequencyMap[currentNum]++;
37 }
38
39 return pairCount;
40 }
41};
42
1/**
2 * Counts the number of pairs in the array where one number can be obtained
3 * from another by swapping at most one pair of digits
4 * @param nums - Array of numbers to process
5 * @returns Number of valid pairs
6 */
7function countPairs(nums: number[]): number {
8 // Sort the array in ascending order
9 nums.sort((a: number, b: number) => a - b);
10
11 // Initialize the answer counter
12 let answer: number = 0;
13
14 // Map to store the frequency of each number encountered so far
15 const frequencyMap: Map<number, number> = new Map<number, number>();
16
17 // Process each number in the sorted array
18 for (const currentNumber of nums) {
19 // Set to store all possible numbers that can be formed by swapping digits
20 const possibleNumbers: Set<number> = new Set<number>();
21
22 // Add the original number itself (no swap case)
23 possibleNumbers.add(currentNumber);
24
25 // Convert number to array of digit characters
26 const digitArray: string[] = currentNumber.toString().split('');
27
28 // Try swapping each pair of digits
29 for (let j: number = 0; j < digitArray.length; j++) {
30 for (let i: number = 0; i < j; i++) {
31 // Swap digits at positions i and j
32 [digitArray[i], digitArray[j]] = [digitArray[j], digitArray[i]];
33
34 // Add the number formed after swapping to the set
35 possibleNumbers.add(Number(digitArray.join('')));
36
37 // Swap back to restore original order
38 [digitArray[i], digitArray[j]] = [digitArray[j], digitArray[i]];
39 }
40 }
41
42 // Count pairs with all previously seen numbers that match any possible swap
43 for (const possibleNumber of possibleNumbers) {
44 answer += frequencyMap.get(possibleNumber) || 0;
45 }
46
47 // Update the frequency of the current number
48 frequencyMap.set(currentNumber, (frequencyMap.get(currentNumber) || 0) + 1);
49 }
50
51 return answer;
52}
53
Time and Space Complexity
Time Complexity: O(n Γ (log n + logΒ³ M))
The time complexity breaks down as follows:
- Sorting the array takes
O(n log n)
time - For each element in the array (
n
iterations):- Converting number to string takes
O(log M)
time (whereM
is the maximum value, as the number of digits is proportional tolog M
) - The nested loops for swapping digits run
O(logΒ² M)
times (choosing 2 positions fromlog M
digits) - Each swap operation and string-to-integer conversion takes
O(log M)
time - The set
vis
can contain at mostO(logΒ² M)
elements (all possible single swaps plus the original) - Summing up counts for elements in
vis
takesO(logΒ² M)
time - Overall per element:
O(logΒ² M Γ log M) = O(logΒ³ M)
- Converting number to string takes
- Total:
O(n log n) + O(n Γ logΒ³ M) = O(n Γ (log n + logΒ³ M))
Space Complexity: O(n + logΒ² M)
The space complexity consists of:
- The
cnt
dictionary stores at mostn
unique elements:O(n)
- The
vis
set for each iteration contains at mostO(logΒ² M)
elements (all possible single swap variations) - The string
s
takesO(log M)
space - Overall:
O(n + logΒ² M)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Sorting the Array First
One of the most critical pitfalls is forgetting to sort the array before processing. Without sorting, you might miss pairs where a larger number can be transformed into a smaller one through digit swapping.
Why it matters: Consider nums = [100, 1]
. If we process left-to-right without sorting:
- When processing
100
, we haven't seen1
yet, so we miss the pair - When processing
1
, we can't "look back" to find100
Solution: Always sort the array first to ensure all potential matches for a number have been processed before it.
2. Incorrect Digit Swapping Logic
A common mistake is not properly restoring the digits after each swap, leading to compound swaps instead of single swaps.
Incorrect approach:
digits = list(str(current_num))
for j in range(len(digits)):
for i in range(j):
digits[i], digits[j] = digits[j], digits[i] # Swap
possible_values.add(int("".join(digits)))
# Forgot to swap back! Next iteration works on modified digits
Solution: Always swap back immediately after recording the result:
digits[i], digits[j] = digits[j], digits[i] # Swap
possible_values.add(int("".join(digits)))
digits[i], digits[j] = digits[j], digits[i] # Swap back
3. Double Counting When Numbers Are Equal
When two numbers in the array are identical, there's a risk of counting them incorrectly if not careful about when to update the frequency counter.
Wrong approach: Updating frequency before checking for matches would cause a number to match with itself.
Solution: Always check for matches with previously seen numbers first, then update the frequency counter for the current number.
4. Not Handling Leading Zeros Correctly
When swapping digits results in leading zeros (e.g., 100
β 001
), the conversion to integer automatically removes them (001
becomes 1
). This is actually the desired behavior, but developers might mistakenly try to preserve leading zeros or handle them separately.
Solution: Trust Python's int()
conversion to handle leading zeros correctly - int("001")
automatically becomes 1
, which is the intended behavior for this problem.
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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!