1577. Number of Ways Where Square of Number Is Equal to Product of Two Numbers
Problem Description
You are given two arrays of integers nums1
and nums2
. Your task is to count the total number of valid triplets that can be formed following two specific rules.
A triplet consists of three indices, and there are two types of valid triplets:
Type 1 Triplet: A triplet (i, j, k)
where:
i
is an index fromnums1
j
andk
are indices fromnums2
withj < k
- The relationship
nums1[i]² == nums2[j] * nums2[k]
must hold
Type 2 Triplet: A triplet (i, j, k)
where:
i
is an index fromnums2
j
andk
are indices fromnums1
withj < k
- The relationship
nums2[i]² == nums1[j] * nums1[k]
must hold
In simpler terms:
- For Type 1: Take one element from
nums1
, square it, and check if it equals the product of any two elements fromnums2
(where the second element comes after the first) - For Type 2: Take one element from
nums2
, square it, and check if it equals the product of any two elements fromnums1
(where the second element comes after the first)
The function should return the total count of all valid triplets (both Type 1 and Type 2 combined).
For example, if nums1 = [7, 4]
and nums2 = [5, 2, 8, 9]
:
- A Type 1 triplet could be
(1, 0, 2)
becausenums1[1]² = 16 = nums2[0] * nums2[2] = 2 * 8
- A Type 2 triplet could be
(3, 0, 1)
becausenums2[3]² = 81 = nums1[0] * nums1[1] = 7 * 4
(this is just an example, not necessarily valid)
Intuition
The key insight is to recognize that we're essentially looking for matching relationships between squares and products across two arrays. Instead of checking every possible triplet combination which would be inefficient, we can precompute all possible products and then match them with squares.
Think about it this way: for Type 1 triplets, we need to find cases where nums1[i]² = nums2[j] * nums2[k]
. Rather than fixing i
and then searching for all valid (j, k)
pairs, we can flip our perspective:
- First, calculate all possible products from pairs in
nums2
(wherej < k
) - Then, for each element in
nums1
, square it and check how many times this squared value appears in our precomputed products
This transforms the problem from a three-way matching problem into a two-step process:
- Step 1: Generate all products from pairs within each array
- Step 2: Match squares from one array with products from the other
The beauty of this approach is that we can use a hash table (Counter) to store the frequency of each product. Why frequency? Because multiple pairs might produce the same product. For instance, 2 * 6 = 12
and 3 * 4 = 12
. If we're looking for triplets where some nums1[i]² = 12
, we need to count both pairs.
By separating the counting of products from the matching with squares, we avoid redundant calculations. We compute all products once for each array, store them with their frequencies, and then simply look up how many times each squared value appears in the opposite array's product table.
This bidirectional approach (Type 1: squares from nums1
matched with products from nums2
, and Type 2: squares from nums2
matched with products from nums1
) ensures we capture all valid triplets while maintaining an efficient time complexity.
Learn more about Math and Two Pointers patterns.
Solution Approach
The implementation uses a hash table-based approach with helper functions to modularize the logic:
Step 1: Create a Product Counting Function
The count
function takes an array and returns a Counter (hash table) that stores all possible products of pairs (j, k)
where j < k
:
def count(nums: List[int]) -> Counter:
cnt = Counter()
for j in range(len(nums)):
for k in range(j + 1, len(nums)):
cnt[nums[j] * nums[k]] += 1
return cnt
This function iterates through all valid pairs in the array. For each pair, it calculates their product and increments the count in the hash table. The time complexity is O(n²)
where n
is the length of the array.
Step 2: Create a Calculation Function
The cal
function takes an array and a Counter, then calculates how many valid triplets can be formed:
def cal(nums: List[int], cnt: Counter) -> int:
return sum(cnt[x * x] for x in nums)
For each element x
in the array, it squares it (x * x
) and looks up how many times this squared value appears in the Counter. The sum of all these counts gives us the total number of valid triplets for this configuration.
Step 3: Apply to Both Types of Triplets
The main solution combines these helper functions:
cnt1 = count(nums1) # All products from pairs in nums1 cnt2 = count(nums2) # All products from pairs in nums2 return cal(nums1, cnt2) + cal(nums2, cnt1)
cal(nums1, cnt2)
: Counts Type 2 triplets - squares fromnums1
matched with products fromnums2
cal(nums2, cnt1)
: Counts Type 1 triplets - squares fromnums2
matched with products fromnums1
Note the seemingly reversed logic here: cal(nums1, cnt2)
actually counts Type 2 triplets because:
- We're squaring elements from
nums1
- We're matching them with products from
nums2
- This corresponds to
nums1[j] * nums1[k] = nums2[i]²
(Type 2)
Time Complexity: O(m² + n²)
where m
and n
are the lengths of nums1
and nums2
respectively, due to generating all pairs.
Space Complexity: O(m² + n²)
for storing the products in the hash tables.
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 small example with nums1 = [7, 4]
and nums2 = [5, 2, 8, 9]
.
Step 1: Count all products from pairs
For nums1 = [7, 4]
:
- Only one pair:
(0, 1)
→7 * 4 = 28
cnt1 = {28: 1}
For nums2 = [5, 2, 8, 9]
:
- Pair
(0, 1)
:5 * 2 = 10
- Pair
(0, 2)
:5 * 8 = 40
- Pair
(0, 3)
:5 * 9 = 45
- Pair
(1, 2)
:2 * 8 = 16
- Pair
(1, 3)
:2 * 9 = 18
- Pair
(2, 3)
:8 * 9 = 72
cnt2 = {10: 1, 40: 1, 45: 1, 16: 1, 18: 1, 72: 1}
Step 2: Match squares with products
For Type 1 triplets (square from nums1, products from nums2):
nums1[0]² = 7² = 49
: Check if 49 exists incnt2
→ No matchnums1[1]² = 4² = 16
: Check if 16 exists incnt2
→ Yes! Count = 1- This gives us triplet
(1, 1, 2)
wherenums1[1]² = 16 = nums2[1] * nums2[2] = 2 * 8
For Type 2 triplets (square from nums2, products from nums1):
nums2[0]² = 5² = 25
: Check if 25 exists incnt1
→ No matchnums2[1]² = 2² = 4
: Check if 4 exists incnt1
→ No matchnums2[2]² = 8² = 64
: Check if 64 exists incnt1
→ No matchnums2[3]² = 9² = 81
: Check if 81 exists incnt1
→ No match
Step 3: Sum up all matches
Total valid triplets = 1 (from Type 1) + 0 (from Type 2) = 1
The algorithm efficiently found the single valid triplet (1, 1, 2)
by precomputing products and matching them with squares, avoiding the need to check all possible three-element combinations.
Solution Implementation
1class Solution:
2 def numTriplets(self, nums1: List[int], nums2: List[int]) -> int:
3 """
4 Count the number of triplets where:
5 - Type 1: nums1[i]^2 = nums2[j] * nums2[k]
6 - Type 2: nums2[i]^2 = nums1[j] * nums1[k]
7 """
8
9 def count_products(nums: List[int]) -> Counter:
10 """
11 Count frequency of all possible products from pairs in the array.
12
13 Args:
14 nums: Input array of integers
15
16 Returns:
17 Counter object with products as keys and frequencies as values
18 """
19 product_counter = Counter()
20
21 # Iterate through all unique pairs (j, k) where j < k
22 for j in range(len(nums)):
23 for k in range(j + 1, len(nums)):
24 product = nums[j] * nums[k]
25 product_counter[product] += 1
26
27 return product_counter
28
29 def calculate_triplets(nums: List[int], product_counter: Counter) -> int:
30 """
31 Calculate number of valid triplets by checking if square of each number
32 exists in the product counter.
33
34 Args:
35 nums: Array containing numbers to square
36 product_counter: Counter of products from the other array
37
38 Returns:
39 Total count of valid triplets
40 """
41 total = 0
42
43 # For each number, check if its square matches any product
44 for num in nums:
45 square = num * num
46 total += product_counter[square]
47
48 return total
49
50 # Count all possible products for both arrays
51 products_nums1 = count_products(nums1)
52 products_nums2 = count_products(nums2)
53
54 # Calculate Type 1 triplets (nums2[i]^2 = nums1[j] * nums1[k])
55 # and Type 2 triplets (nums1[i]^2 = nums2[j] * nums2[k])
56 type1_triplets = calculate_triplets(nums2, products_nums1)
57 type2_triplets = calculate_triplets(nums1, products_nums2)
58
59 return type1_triplets + type2_triplets
60
1class Solution {
2 /**
3 * Counts the number of Type 1 and Type 2 triplets.
4 * Type 1: i from nums1, j and k from nums2, where nums1[i]^2 = nums2[j] * nums2[k]
5 * Type 2: i from nums2, j and k from nums1, where nums2[i]^2 = nums1[j] * nums1[k]
6 *
7 * @param nums1 First array of integers
8 * @param nums2 Second array of integers
9 * @return Total count of Type 1 and Type 2 triplets
10 */
11 public int numTriplets(int[] nums1, int[] nums2) {
12 // Count all possible products of pairs in each array
13 Map<Long, Integer> productCountMap1 = count(nums1);
14 Map<Long, Integer> productCountMap2 = count(nums2);
15
16 // Calculate Type 1 triplets (square from nums1, pair from nums2)
17 // and Type 2 triplets (square from nums2, pair from nums1)
18 return cal(productCountMap1, nums2) + cal(productCountMap2, nums1);
19 }
20
21 /**
22 * Counts the frequency of all possible products of pairs in the array.
23 *
24 * @param nums Input array
25 * @return Map where key is the product and value is its frequency
26 */
27 private Map<Long, Integer> count(int[] nums) {
28 Map<Long, Integer> productCount = new HashMap<>();
29 int n = nums.length;
30
31 // Iterate through all pairs (j, k) where j < k
32 for (int j = 0; j < n; ++j) {
33 for (int k = j + 1; k < n; ++k) {
34 // Calculate product and cast to long to avoid overflow
35 long product = (long) nums[j] * nums[k];
36 // Increment the count for this product
37 productCount.merge(product, 1, Integer::sum);
38 }
39 }
40
41 return productCount;
42 }
43
44 /**
45 * Calculates the number of valid triplets where the square of elements
46 * from nums matches the products in the count map.
47 *
48 * @param productCount Map of products and their frequencies
49 * @param nums Array whose elements will be squared
50 * @return Number of valid triplets
51 */
52 private int cal(Map<Long, Integer> productCount, int[] nums) {
53 int tripletCount = 0;
54
55 // For each number in nums, check if its square exists in the product map
56 for (int num : nums) {
57 long square = (long) num * num;
58 // Add the frequency of this square value to the result
59 tripletCount += productCount.getOrDefault(square, 0);
60 }
61
62 return tripletCount;
63 }
64}
65
1class Solution {
2public:
3 int numTriplets(vector<int>& nums1, vector<int>& nums2) {
4 // Count all possible products of pairs from each array
5 auto pairProductCount1 = countPairProducts(nums1);
6 auto pairProductCount2 = countPairProducts(nums2);
7
8 // Calculate Type 1 triplets (nums1[i]^2 = nums2[j] * nums2[k])
9 // and Type 2 triplets (nums2[i]^2 = nums1[j] * nums1[k])
10 return calculateTriplets(pairProductCount1, nums2) +
11 calculateTriplets(pairProductCount2, nums1);
12 }
13
14private:
15 // Count frequency of all possible products from pairs in the array
16 unordered_map<long long, int> countPairProducts(vector<int>& nums) {
17 unordered_map<long long, int> productFrequency;
18
19 // Iterate through all unique pairs (i, j) where i < j
20 for (int i = 0; i < nums.size(); i++) {
21 for (int j = i + 1; j < nums.size(); j++) {
22 // Store the product as long long to avoid overflow
23 long long product = static_cast<long long>(nums[i]) * nums[j];
24 productFrequency[product]++;
25 }
26 }
27
28 return productFrequency;
29 }
30
31 // Calculate number of valid triplets where nums[i]^2 equals a pair product
32 int calculateTriplets(unordered_map<long long, int>& pairProductCount,
33 vector<int>& nums) {
34 int tripletCount = 0;
35
36 // For each number in nums, check if its square exists in pair products
37 for (int num : nums) {
38 long long squaredValue = static_cast<long long>(num) * num;
39
40 // Add the frequency of this squared value in pair products
41 if (pairProductCount.find(squaredValue) != pairProductCount.end()) {
42 tripletCount += pairProductCount[squaredValue];
43 }
44 }
45
46 return tripletCount;
47 }
48};
49
1/**
2 * Counts the number of triplets where nums1[i]^2 = nums2[j] * nums2[k]
3 * and nums2[i]^2 = nums1[j] * nums1[k]
4 * @param nums1 - First array of numbers
5 * @param nums2 - Second array of numbers
6 * @returns Total count of valid triplets
7 */
8function numTriplets(nums1: number[], nums2: number[]): number {
9 // Count all products of pairs in each array
10 const productCount1: Map<number, number> = count(nums1);
11 const productCount2: Map<number, number> = count(nums2);
12
13 // Calculate triplets where square from nums2 equals product from nums1
14 // and square from nums1 equals product from nums2
15 return cal(productCount1, nums2) + cal(productCount2, nums1);
16}
17
18/**
19 * Creates a frequency map of all products from pairs of elements in the array
20 * @param nums - Input array of numbers
21 * @returns Map where key is product and value is frequency
22 */
23function count(nums: number[]): Map<number, number> {
24 const productFrequencyMap: Map<number, number> = new Map();
25
26 // Iterate through all pairs (j, k) where j < k
27 for (let j = 0; j < nums.length; ++j) {
28 for (let k = j + 1; k < nums.length; ++k) {
29 const product: number = nums[j] * nums[k];
30 // Increment the frequency count for this product
31 productFrequencyMap.set(product, (productFrequencyMap.get(product) || 0) + 1);
32 }
33 }
34
35 return productFrequencyMap;
36}
37
38/**
39 * Calculates the number of valid triplets by matching squares with products
40 * @param cnt - Map of products and their frequencies
41 * @param nums - Array to check squares against the products
42 * @returns Count of matching triplets
43 */
44function cal(cnt: Map<number, number>, nums: number[]): number {
45 // For each number, check if its square exists in the product map
46 // and accumulate the frequency count
47 return nums.reduce((accumulator: number, currentValue: number) => {
48 const square: number = currentValue * currentValue;
49 return accumulator + (cnt.get(square) || 0);
50 }, 0);
51}
52
Time and Space Complexity
Time Complexity: O(m^2 + n^2 + m + n)
The time complexity breaks down as follows:
- The
count
function is called twice - once fornums1
and once fornums2
- For
nums1
: The nested loops iterate through all pairs(j, k)
wherej < k
, resulting inO(m^2)
operations - For
nums2
: Similarly, this takesO(n^2)
operations
- For
- The
cal
function is called twice- First call iterates through
nums1
and looks up values incnt2
, takingO(m)
time - Second call iterates through
nums2
and looks up values incnt1
, takingO(n)
time
- First call iterates through
- Total time complexity:
O(m^2) + O(n^2) + O(m) + O(n) = O(m^2 + n^2 + m + n)
Space Complexity: O(m^2 + n^2)
The space complexity is determined by:
cnt1
stores all possible products of pairs fromnums1
. In the worst case, all pairs produce unique products, resulting inO(m^2)
spacecnt2
stores all possible products of pairs fromnums2
. Similarly, this requiresO(n^2)
space in the worst case- Total space complexity:
O(m^2) + O(n^2) = O(m^2 + n^2)
Where m
is the length of nums1
and n
is the length of nums2
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusion About Type 1 vs Type 2 Mapping
The Pitfall:
The most common mistake is getting confused about which calculation corresponds to which triplet type. In the code, calculate_triplets(nums2, products_nums1)
actually counts Type 1 triplets, not Type 2. This seems counterintuitive because:
- Type 1 is defined as:
nums1[i]² == nums2[j] * nums2[k]
- But we're calling
calculate_triplets(nums2, products_nums1)
Many developers mistakenly think they should square elements from nums1
for Type 1 triplets.
Why This Happens: The confusion arises from misreading the problem statement. For Type 1:
- We need ONE element from
nums1
(at indexi
) - We need TWO elements from
nums2
(at indicesj
andk
) - So we square the single element and match it against products of pairs
The Solution: Remember the pattern:
- When you need to match one squared element from array A with products of pairs from array B
- You compute products from B and square elements from A
- The array providing the single element gets squared; the array providing pairs gets multiplied
2. Integer Overflow Issues
The Pitfall: When dealing with large integers, squaring them or multiplying pairs can cause overflow issues. For example:
- If
nums1 = [100000]
andnums2 = [100000, 1]
- Computing
100000²
=10,000,000,000
which exceeds 32-bit integer limits
The Solution: In Python, this is handled automatically as integers can be arbitrarily large. However, in other languages like Java or C++, you should:
# Use long/int64 for calculations
square = int(num) * int(num) # Explicit casting if needed
product = int(nums[j]) * int(nums[k])
3. Counting Duplicate Products Incorrectly
The Pitfall: Not properly handling cases where the same product can be formed by different pairs. For example:
nums = [2, 3, 4, 6]
- Product 12 can be formed by:
(2, 6)
and(3, 4)
- Both should be counted separately
The Solution: The Counter approach handles this correctly by incrementing the count for each occurrence:
product_counter[product] += 1 # Correctly accumulates all occurrences
4. Including Invalid Pairs (j >= k)
The Pitfall:
Accidentally counting pairs where j >= k
or counting the same pair twice (once as (j,k)
and once as (k,j)
).
Incorrect approach:
# Wrong: This counts each pair twice
for j in range(len(nums)):
for k in range(len(nums)):
if j != k: # Still wrong!
product_counter[nums[j] * nums[k]] += 1
The Solution:
Always ensure j < k
in your loop structure:
for j in range(len(nums)):
for k in range(j + 1, len(nums)): # Ensures j < k
product_counter[nums[j] * nums[k]] += 1
5. Edge Cases with Empty or Single-Element Arrays
The Pitfall: Not handling cases where:
- One or both arrays are empty
- An array has only one element (can't form pairs)
The Solution: The current implementation handles these gracefully:
- Empty array → No pairs → Empty Counter → Zero triplets
- Single element → No valid pairs where j < k → Zero products → Zero triplets
However, you could add explicit checks for clarity:
if len(nums1) < 2 and len(nums2) < 2:
return 0 # No valid triplets possible
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
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
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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
Want a Structured Path to Master System Design Too? Don’t Miss This!