Count Almost Equal Pairs II
HARDDescription
Attention: In this version, the number of operations that can be performed, has been increased to twice.
You are given an array nums consisting of positive integers.
We call two integers x and y almost equal if both integers can become equal after performing the following operation at most twice:
- Choose either
xoryand swap any two digits within the chosen number.
Return the number of indices i and j in nums where i < j such that nums[i] and nums[j] are almost equal.
Note that it is allowed for an integer to have leading zeros after performing an operation.
Example 1:
Input: nums = [1023,2310,2130,213]
Output: 4
Explanation:
The almost equal pairs of elements are:
- 1023 and 2310. By swapping the digits 1 and 2, and then the digits 0 and 3 in 1023, you get 2310.
- 1023 and 213. By swapping the digits 1 and 0, and then the digits 1 and 2 in 1023, you get 0213, which is 213.
- 2310 and 213. By swapping the digits 2 and 0, and then the digits 3 and 2 in 2310, you get 0213, which is 213.
- 2310 and 2130. By swapping the digits 3 and 1 in 2310, you get 2130.
Example 2:
Input: nums = [1,10,100]
Output: 3
Explanation:
The almost equal pairs of elements are:
- 1 and 10. By swapping the digits 1 and 0 in 10, you get 01 which is 1.
- 1 and 100. By swapping the second 0 with the digit 1 in 100, you get 001, which is 1.
- 10 and 100. By swapping the first 0 with the digit 1 in 100, you get 010, which is 10.
Constraints:
2 <= nums.length <= 50001 <= nums[i] < 107
Approaches
Checkout 2 different approaches to solve Count Almost Equal Pairs II. Click on different approaches to view the approach and algorithm in detail.
Grouping by Permutation and Hashing Variants
A more efficient approach avoids the quadratic complexity by first grouping numbers that are permutations of each other. Since only numbers within the same permutation group can be almost equal, we can process each group independently. Within a group, for each number, we generate all its 'almost equal' variants and use a hash map to quickly count how many previously seen numbers match these variants. This reduces the overall complexity from quadratic to linear.
Algorithm
- First, preprocess the input array. Convert each number to a string of fixed length (e.g., 7) by padding with leading zeros. Then, create a canonical key for each number by sorting its digits. Use a hash map
groupsto group all numbers that have the same key (i.e., are permutations of each other). - Initialize
total_pairs = 0. - Iterate through each list of numbers (
group) in thegroupsmap. - For each
group, create a new hash mapprocessed_countsto store the frequency of numbers encountered so far within that group. - Iterate through each number
sin the currentgroup:- Generate a set
S(s)containing all unique strings that can be formed fromsby performing 0, 1, or 2 swaps.- 0 swaps:
sitself. - 1 swap: All permutations that are single transpositions.
- 2 swaps: All permutations that are 3-cycles or compositions of two disjoint 2-cycles.
- 0 swaps:
- For each
variantin the generated setS(s):- Add the count of
processed_counts.getOrDefault(variant, 0)tototal_pairs. This finds pairs with already processed numbers.
- Add the count of
- After checking all variants for
s, increment its count inprocessed_counts:processed_counts.put(s, processed_counts.getOrDefault(s, 0) + 1).
- Generate a set
- Return
total_pairs.
This method is based on the observation that two numbers can only be almost equal if they are permutations of each other. We can leverage this to optimize the search.
-
Grouping: We first iterate through the
numsarray. For each number, we create a canonical representation by sorting its digits (after padding it to a fixed length string). We use aHashMap<String, List<String>>to group all numbers that share the same canonical form. -
Counting within Groups: We then iterate through each group of numbers. For a given group, we know all numbers are permutations of one another. We can count the almost equal pairs within this group efficiently. We use another
HashMap<String, Integer>calledprocessed_countsto keep track of the numbers we've seen so far in the group and their frequencies. -
Variant Generation and Hashing: For each number
sin the group, we generate all possible strings that can be obtained by applying at most two swaps. This set of variants,S(s), represents all numbers that are almost equal tos. For everyvariantinS(s), we check if it exists inprocessed_counts. If it does, it means we've already processed a number that is almost equal to our current numbers. We add its frequency fromprocessed_countsto our total pair count. After processing all variants ofs, we update the frequency ofsitself inprocessed_counts.
This process ensures that each valid pair (i, j) with i < j is counted exactly once. The time complexity is dominated by iterating through the numbers and generating their variants, which is much faster than comparing all pairs.
import java.util.*;
class Solution {
public int countAlmostEqualPairs(int[] nums) {
Map<String, List<String>> groups = new HashMap<>();
int maxLen = 0;
for (int num : nums) {
maxLen = Math.max(maxLen, String.valueOf(num).length());
}
for (int num : nums) {
String s = String.valueOf(num);
s = String.format("%" + maxLen + "s", s).replace(' ', '0');
char[] chars = s.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
groups.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
long totalPairs = 0;
for (List<String> group : groups.values()) {
Map<String, Integer> processedCounts = new HashMap<>();
for (String s : group) {
Set<String> variants = generateVariants(s);
for (String variant : variants) {
totalPairs += processedCounts.getOrDefault(variant, 0);
}
processedCounts.put(s, processedCounts.getOrDefault(s, 0) + 1);
}
}
return (int) totalPairs;
}
private Set<String> generateVariants(String s) {
Set<String> variants = new HashSet<>();
char[] arr = s.toCharArray();
int n = arr.length;
// 0 swaps
variants.add(s);
// 1 swap (transpositions)
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
swap(arr, i, j);
variants.add(new String(arr));
swap(arr, i, j); // backtrack
}
}
// 2 swaps (3-cycles and two 2-cycles)
// This can be done by applying a second swap to all 1-swap variants.
Set<String> oneSwapVariants = new HashSet<>(variants);
for(String oneSwapStr : oneSwapVariants){
if(oneSwapStr.equals(s)) continue;
char[] tempArr = oneSwapStr.toCharArray();
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
swap(tempArr, i, j);
variants.add(new String(tempArr));
swap(tempArr, i, j); // backtrack
}
}
}
return variants;
}
private void swap(char[] arr, int i, int j) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
Complexity Analysis
Pros and Cons
- Much more efficient, with a time complexity linear in the input size.
- Scales well for larger inputs compared to the brute-force method.
- More complex to implement, particularly the generation of all variants.
- Requires more memory to store the groups of numbers and the counts of processed numbers.
Code Solutions
Checking out 3 solutions in different languages for Count Almost Equal Pairs II. Click on different languages to view the code.
class Solution {
public
int countPairs(int[] nums) {
Arrays.sort(nums);
int ans = 0;
Map<Integer, Integer> cnt = new HashMap<>();
for (int x : nums) {
Set<Integer> vis = new HashSet<>();
vis.add(x);
char[] s = String.valueOf(x).toCharArray();
for (int j = 0; j < s.length; ++j) {
for (int i = 0; i < j; ++i) {
swap(s, i, j);
vis.add(Integer.parseInt(String.valueOf(s)));
for (int q = i; q < s.length; ++q) {
for (int p = i; p < q; ++p) {
swap(s, p, q);
vis.add(Integer.parseInt(String.valueOf(s)));
swap(s, p, q);
}
}
swap(s, i, j);
}
}
for (int y : vis) {
ans += cnt.getOrDefault(y, 0);
}
cnt.merge(x, 1, Integer : : sum);
}
return ans;
}
private
void swap(char[] s, int i, int j) {
char t = s[i];
s[i] = s[j];
s[j] = t;
}
}
Video Solution
Watch the video walkthrough for Count Almost Equal Pairs II
Similar Questions
5 related questions you might find useful
Algorithms:
Patterns:
Data Structures:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.