Given two integer arrays like this:-
int[] a = { 2, 6, 10, 13, 17,18 };
int[] b = { 3, 7, 8, 9, 11, 15 };
How can I find pairs from these two arrays such that when multiplied they become perfect square?
For eg, in above arrays {2,8}
& {18,8}
are two pairs.
Right now my approach is brute-force, where I am looping through both arrays like this:-
int count = 0;
for (int i = 0; i < arr1.Length; i++)
{
for (int j = 0; j < arr2.Length; j++)
{
var x = arr1[i] * arr2[j];
long s = (long)Math.Sqrt(x);
if (x == s * s)
count += 1;
}
}
How can I do this efficiently?
Any two numbers where the count of each prime factor in the number is even, would form a valid pair. Otherwise, any number with an odd count of one or more prime factors could only pair with another number with the exact same factors having an odd count. This means all we would need to store for each number is which of its prime factors have an odd count. This could be hashed.
a = { 2, 6, 10, 13, 17,18 };
b = { 3, 7, 8, 9, 11, 15 };
Hash the shorter array where the key is the combination of prime factors with odd count and the value is the corresponding list of indexes in the array:
a = {
2: [0,5],
2-3: [1],
2-5: [2],
13: [3],
17: [4]
}
Traverse the second array and exactly match the prime-factor-with-odd-count-combination:
b[0] -> 3, no match
b[1] -> 7, no match
b[2] -> 2, match with a[0] and a[5] -> 2 * 8 and 18 * 8 are perfect squares
b[3] -> None, no match
b[4] -> 11, no match
b[5] -> 3-5, no match
For ordered (only matters for the detection of the max) arrays with integers > 0 with the help of a modified sieve of Eratosthenes, I think the complexity is O(max * log(max) * log(log(max)) + n) and there is O(max) extra space required (which can be a huge improvement or very bad depending on n and max compared to the standard O(n^2) with O(1) space and no dependency on max):
long total = 0;
int max = (a[a.length - 1] > b[b.length - 1] ? a[a.length - 1] : b[b.length - 1]) + 1;
int[] sieve = new int[max], anti = new int[max], count = new int[max];
for (int i = 1; i < max; i++) {
sieve[i] = i; // using numbers and divide by prime instead of booleans
anti[i] = 1; // number by which i has to be multiplied to get the smallest square
}
for (int i = 2; i < max; i++) {
if (sieve[i] == i) { // i is prime
for (int j = i; j < max; j += i) {
boolean odd = false;
do {
odd = !odd;
sieve[j] /= i;
} while (sieve[j] % i == 0);
if (odd)
anti[j] *= i;
}
} // if you know the max over all the calls the above has only to be done once
} // except for resetting count and total, so we would get O(n) because max is constant
for (int i = 0; i < a.length; i++)
count[anti[a[i]]]++; // hash map for count is better than array if n < max
for (int i = 0; i < b.length; i++)
total += count[anti[b[i]]];
A square number will be mapped to 1 and others to the product of primes with odd exponents in the factorization of the number. In your example every number is mapped to itself except for 8 => 2, 9 => 1, 18 => 2. Other interesting numbers below 18 (not square or mapping to itself): 12 => 3. When iterating over a the algorithm increases the count of 2 once when visiting 2 and once when visiting 18. The total will be increased by 2 when the iteration over b visits 8, the only matches between the two arrays and therefore the end result.
I managed to simplify it using LINQ:
int[] arr1 = { 2, 6, 10, 13, 17, 18 };
int[] arr2 = { 3, 7, 8, 9, 11, 15 };
int count = arr1.Sum(t => (from t1 in arr2 select t*t1 into x let s = (long) Math.Sqrt(x) where x == s*s select x).Count());
I would check whether you have any decimal place after drawing the root left. If not then you have your perfect squareroot:
void Main()
{
int[] arr1 = { 2, 6, 10, 13, 17, 18 };
int[] arr2 = { 3, 7, 8, 9, 11, 15 };
List<string> PairList = new List<string>();
for (int i = 0; i < arr1.Length; i++)
{
for (int j = 0; j < arr2.Length; j++)
{
if (Math.Sqrt(arr1[i] * arr2[j]) % 1 == 0)
{
PairList.Add(String.Format("{0} & {1}", arr1[i] , arr2[j]));
}
}
}
PairList.Dump();
}