Number of Ways Where Square of Number Is Equal to Product of Two Numbers
MedPrompt
Given two arrays of integers nums1 and nums2, return the number of triplets formed (type 1 and type 2) under the following rules:
- Type 1: Triplet (i, j, k) if
nums1[i]2 == nums2[j] * nums2[k]where0 <= i < nums1.lengthand0 <= j < k < nums2.length. - Type 2: Triplet (i, j, k) if
nums2[i]2 == nums1[j] * nums1[k]where0 <= i < nums2.lengthand0 <= j < k < nums1.length.
Example 1:
Input: nums1 = [7,4], nums2 = [5,2,8,9]
Output: 1
Explanation: Type 1: (1, 1, 2), nums1[1]2 = nums2[1] * nums2[2]. (42 = 2 * 8). Example 2:
Input: nums1 = [1,1], nums2 = [1,1,1]
Output: 9
Explanation: All Triplets are valid, because 12 = 1 * 1.
Type 1: (0,0,1), (0,0,2), (0,1,2), (1,0,1), (1,0,2), (1,1,2). nums1[i]2 = nums2[j] * nums2[k].
Type 2: (0,0,1), (1,0,1), (2,0,1). nums2[i]2 = nums1[j] * nums1[k].Example 3:
Input: nums1 = [7,7,8,3], nums2 = [1,2,9,7]
Output: 2
Explanation: There are 2 valid triplets.
Type 1: (3,0,2). nums1[3]2 = nums2[0] * nums2[2].
Type 2: (3,0,1). nums2[3]2 = nums1[0] * nums1[1].
Constraints:
1 <= nums1.length, nums2.length <= 10001 <= nums1[i], nums2[i] <= 105
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves iterating through all possible triplets (i, j, k) for both Type 1 and Type 2, checking if the condition num^2 = product holds for each triplet. This is the most straightforward but also the slowest method.
Algorithm
- The problem can be broken down into two symmetric subproblems: counting Type 1 triplets and counting Type 2 triplets. The total count is the sum of these two.
- We can create a helper function, say
count(arr1, arr2), which counts triplets where a square of a number fromarr1equals the product of two numbers fromarr2. The final answer would becount(nums1, nums2) + count(nums2, nums1). - The
countfunction iterates through every elementarr1[i]. For eacharr1[i], it then iterates through all possible pairs(arr2[j], arr2[k])withj < k. - Inside the innermost loop, it checks if
(long)arr1[i] * arr1[i] == (long)arr2[j] * arr2[k]. If the condition is true, a counter is incremented. - Using
longfor calculations is crucial to prevent integer overflow, as numbers can be up to 10^5, and their square can be up to 10^10.
Walkthrough
The core idea is to check every single combination of indices (i, j, k) that satisfy the problem's constraints. We can implement a helper function that takes two arrays, arr1 and arr2, and counts the number of triplets where the square of an element from arr1 equals the product of two distinct elements from arr2. By calling this helper function twice with the arguments swapped (count(nums1, nums2) and count(nums2, nums1)), we can find the total number of valid triplets.
class Solution { public int numTriplets(int[] nums1, int[] nums2) { return countTriplets(nums1, nums2) + countTriplets(nums2, nums1); } private int countTriplets(int[] arr1, int[] arr2) { int count = 0; int n = arr1.length; int m = arr2.length; if (m < 2) { return 0; } for (int i = 0; i < n; i++) { long square = (long) arr1[i] * arr1[i]; for (int j = 0; j < m; j++) { for (int k = j + 1; k < m; k++) { if (square == (long) arr2[j] * arr2[k]) { count++; } } } } return count; }}Complexity
Time
O(N * M^2 + M * N^2), where N is the length of `nums1` and M is the length of `nums2`. The helper function `count(arr1, arr2)` takes `O(N * M^2)`. The total time is the sum for both types of triplets. This is too slow for the given constraints.
Space
O(1), as we only use a few variables to store counts and intermediate values.
Trade-offs
Pros
Simple to understand and implement.
Cons
Very inefficient and will likely result in a "Time Limit Exceeded" error for the given constraints.
This approach improves upon the brute-force method by optimizing the search for pairs. For each number from the first array, we find pairs in the second array whose product equals the square of the number. By sorting the second array, we can use a two-pointer technique to find these pairs efficiently in linear time relative to the size of the second array.
Algorithm
- Define a helper function
count(arr1, arr2). - Sort
arr2. - Initialize
total_triplets = 0. - For each
num1inarr1:- Calculate
target = (long)num1 * num1. - Initialize two pointers on
arr2:left = 0,right = arr2.length - 1. - While
left < right:- Calculate
product = (long)arr2[left] * arr2[right]. - If
product < target, incrementleft. - Else if
product > target, decrementright. - Else (
product == target):- If
arr2[left] == arr2[right], all elements between them are the same. The number of pairs isk * (k-1) / 2wherekisright - left + 1. Add this to the count and break the inner loop. - If
arr2[left] != arr2[right], count the number of duplicates forarr2[left]andarr2[right], multiply these counts, add to the total, and move the pointers past the duplicates.
- If
- Calculate
- Calculate
- Return
total_triplets. - The main function returns
count(nums1, nums2) + count(nums2, nums1).
Walkthrough
We can reframe the problem for a fixed num1 from the first array as a 'Two Sum'-like problem on the second array. We need to find pairs (num2_j, num2_k) such that their product is num1^2. If the second array is sorted, this can be solved efficiently using two pointers.
The overall strategy is to have a helper function that first sorts the second array (arr2). Then, for each element in the first array (arr1), it uses a two-pointer scan (left and right) on arr2 to find pairs whose product matches the target square. A key part of this approach is correctly handling duplicate numbers to ensure every valid pair is counted exactly once.
import java.util.Arrays; class Solution { public int numTriplets(int[] nums1, int[] nums2) { return countTriplets(nums1, nums2) + countTriplets(nums2, nums1); } private int countTriplets(int[] arr1, int[] arr2) { Arrays.sort(arr2); int count = 0; int n = arr1.length; int m = arr2.length; if (m < 2) { return 0; } for (int i = 0; i < n; i++) { long target = (long) arr1[i] * arr1[i]; int left = 0, right = m - 1; while (left < right) { long product = (long) arr2[left] * arr2[right]; if (product < target) { left++; } else if (product > target) { right--; } else { // product == target if (arr2[left] == arr2[right]) { int numElements = right - left + 1; count += numElements * (numElements - 1) / 2; break; // All elements in between are the same } int leftCount = 1; while (left + 1 < right && arr2[left] == arr2[left + 1]) { leftCount++; left++; } int rightCount = 1; while (right - 1 > left && arr2[right] == arr2[right - 1]) { rightCount++; right--; } count += leftCount * rightCount; left++; right--; } } } return count; }}Complexity
Time
O(N * M + M * log M + M * N + N * log N). Sorting takes `O(M log M)` and `O(N log N)`. The nested loops take `O(N * M)` and `O(M * N)`. This simplifies to `O(N*M)`.
Space
O(log N + log M) or O(N + M) depending on the space used by the sorting algorithm.
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
It's an in-place approach if the sorting algorithm used is in-place (modifying the input array).
Cons
The logic for handling duplicates in the two-pointer scan can be complex to implement correctly.
Its worst-case time complexity is similar to the hash map approach but it doesn't benefit from a small number of unique elements.
This is the most efficient approach. Instead of repeatedly scanning the second array to find pairs, we can pre-process it and store the frequency of each number in a hash map. This allows for a constant-time lookup to find the required number to form a product, significantly speeding up the process.
Algorithm
- Define a helper function
count(arr1, arr2). - Create a frequency map
freq2for all numbers inarr2. - Initialize
total_triplets = 0. - For each
num1inarr1:- Calculate
target = (long)num1 * num1. - For each unique number
num2and itscount2infreq2:- If
targetis divisible bynum2:- Calculate
required = target / num2. - If
requiredexists infreq2:- If
num2 == required, addcount2 * (count2 - 1) / 2tototal_triplets. - If
num2 < required, addcount2 * freq2.get(required)tototal_tripletsto avoid double counting.
- If
- Calculate
- If
- Calculate
- Return
total_triplets. - The main function returns
count(nums1, nums2) + count(nums2, nums1).
Walkthrough
The key optimization here is to avoid re-computing information. For a given target square, we need to find pairs in the second array. Instead of a linear scan or a two-pointer scan for every target, we can count the occurrences of each number in the second array and store them in a hash map.
Then, for each number num1 in the first array, we iterate through the unique numbers num2 in the hash map. For each num2, we can calculate the required number that would satisfy num2 * required = num1^2. We can then check the hash map for the existence and frequency of this required number. Special care is taken to handle cases where num2 is equal to required (we need to choose two from the same group of numbers) and to avoid double-counting pairs like (a, b) and (b, a).
import java.util.HashMap;import java.util.Map; class Solution { public int numTriplets(int[] nums1, int[] nums2) { return (int) (countTriplets(nums1, nums2) + countTriplets(nums2, nums1)); } private long countTriplets(int[] arr1, int[] arr2) { Map<Integer, Integer> freq2 = new HashMap<>(); for (int num : arr2) { freq2.put(num, freq2.getOrDefault(num, 0) + 1); } long count = 0; for (int num1 : arr1) { long target = (long) num1 * num1; for (Map.Entry<Integer, Integer> entry : freq2.entrySet()) { long num2 = entry.getKey(); long count2 = entry.getValue(); if (target % num2 != 0) { continue; } long requiredNum = target / num2; if (freq2.containsKey((int)requiredNum)) { if (num2 == requiredNum) { count += count2 * (count2 - 1) / 2; } else if (num2 < requiredNum) { count += count2 * freq2.get((int)requiredNum); } } } } return count; }}Complexity
Time
O(N * U2 + M * U1), where N and M are the lengths of the arrays, and U1 and U2 are the number of unique elements in `nums1` and `nums2` respectively. Building the map takes `O(M)`. The nested loops take `O(N * U2)`. The total complexity is the sum for both types. In the worst case (`U1=N, U2=M`), it's `O(N*M)`, but it's much faster if there are many duplicate numbers.
Space
O(U1 + U2) to store the frequency maps for both arrays, where U1 and U2 are the number of unique elements in `nums1` and `nums2`.
Trade-offs
Pros
Most efficient approach, especially when the input arrays contain many duplicate values.
The logic is relatively straightforward compared to the two-pointer duplicate handling.
Cons
Requires extra space for the hash maps, proportional to the number of unique elements.
Solutions
Solution
class Solution {public int numTriplets(int[] nums1, int[] nums2) { Map<Integer, Integer> cnt1 = new HashMap<>(); Map<Integer, Integer> cnt2 = new HashMap<>(); for (int v : nums1) { cnt1.put(v, cnt1.getOrDefault(v, 0) + 1); } for (int v : nums2) { cnt2.put(v, cnt2.getOrDefault(v, 0) + 1); } long ans = 0; for (var e1 : cnt1.entrySet()) { long a = e1.getKey(), x = e1.getValue(); for (var e2 : cnt2.entrySet()) { long b = e2.getKey(), y = e2.getValue(); if ((a * a) % b == 0) { long c = a * a / b; if (b == c) { ans += x * y * (y - 1); } else { ans += x * y * cnt2.getOrDefault((int)c, 0); } } if ((b * b) % a == 0) { long c = b * b / a; if (a == c) { ans += x * (x - 1) * y; } else { ans += x * y * cnt1.getOrDefault((int)c, 0); } } } } return (int)(ans >> 1); }}class Solution {public: int numTriplets(vector<int> &nums1, vector<int> &nums2) { auto cnt1 = count(nums1); auto cnt2 = count(nums2); return cal(cnt1, nums2) + cal(cnt2, nums1); } unordered_map<long long, int> count(vector<int> &nums) { unordered_map<long long, int> cnt; for (int i = 0; i < nums.size(); i++) { for (int j = i + 1; j < nums.size(); j++) { cnt[(long long)nums[i] * nums[j]]++; } } return cnt; } int cal(unordered_map<long long, int> &cnt, vector<int> &nums) { int ans = 0; for (int x : nums) { ans += cnt[(long long)x * x]; } return ans; }};class Solution: def numTriplets(self, nums1: List[int], nums2: List[int]) -> int: cnt1 = Counter(nums1) cnt2 = Counter(nums2) ans = 0 for a, x in cnt1 . items(): for b, y in cnt2 . items(): if a * a % b == 0: c = a * a // b if b == c: ans += x * y * (y - 1) else: ans += x * y * cnt2[c] if b * b % a == 0: c = b * b // a if a == c: ans += x * (x - 1) * y else: ans += x * y * cnt1[c] return ans >> 1Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.