3411. Maximum Subarray With Equal Products
Problem Description
You are given an array of positive integers nums
.
The problem introduces a special property called product equivalent. An array arr
is product equivalent if it satisfies the equation:
prod(arr) == lcm(arr) * gcd(arr)
Where:
prod(arr)
is the product of all elements in the array (multiply all numbers together)gcd(arr)
is the Greatest Common Divisor of all elements in the arraylcm(arr)
is the Least Common Multiple of all elements in the array
Your task is to find the length of the longest contiguous subarray within nums
that has this product equivalent property.
For example, if we have a subarray [a, b, c]
, it would be product equivalent if:
a * b * c == lcm(a, b, c) * gcd(a, b, c)
The solution iterates through all possible subarrays, checking each one to see if it satisfies the product equivalent condition. For each starting position i
, it extends the subarray by adding elements one by one, updating the product, GCD, and LCM incrementally. When a subarray satisfies the condition p == g * l
, it updates the maximum length found. The optimization if p > max_p: break
helps prune the search when the product becomes too large to possibly satisfy the condition.
Intuition
The key insight is understanding when the equation prod(arr) == lcm(arr) * gcd(arr)
holds true. This relationship is special because for most arrays, the product is actually larger than lcm * gcd
.
Consider what happens with the prime factorizations. For any number, we can express it as a product of prime powers. When calculating:
- The GCD takes the minimum power of each prime across all numbers
- The LCM takes the maximum power of each prime across all numbers
- The product adds up all the powers of each prime
For the equation to hold, the sum of all prime powers (from the product) must equal the sum of the minimum and maximum powers (from gcd * lcm
). This happens in special cases, like when we have only one or two distinct numbers, or when the numbers share specific divisibility relationships.
Since we need to find the longest subarray with this property, we must check all possible subarrays. The brute force approach of checking every subarray is actually reasonable here because:
- We can compute the product, GCD, and LCM incrementally as we extend each subarray, avoiding redundant calculations
- The product grows exponentially, so we can add an optimization to break early when the product becomes too large
The pruning condition if p > max_p: break
is clever - we precompute an upper bound max_p = lcm(*nums) * max(nums)
which represents a theoretical maximum for lcm * gcd
of any subarray. If our current product exceeds this, there's no way it can equal lcm * gcd
for any extension of the current subarray, so we can stop early.
This approach efficiently explores all possibilities while cutting off branches that cannot lead to valid solutions.
Learn more about Math and Sliding Window patterns.
Solution Approach
The solution uses a nested loop approach to check all possible subarrays, with optimizations to make it efficient.
Algorithm Structure:
-
Initialize variables:
n = len(nums)
to store the array lengthans = 0
to track the maximum length foundmax_p = lcm(*nums) * max(nums)
as an upper bound for pruning
-
Outer loop (starting position
i
): For each positioni
from 0 to n-1, we consider it as the start of a potential subarray. -
Initialize tracking variables for each starting position:
p = 1
for the running productg = 0
for the running GCD (0 acts as identity for GCD with positive numbers)l = 1
for the running LCM
-
Inner loop (extending the subarray): For each position
j
fromi
to n-1:- Update the product:
p *= nums[j]
- Update the GCD:
g = gcd(g, nums[j])
- Update the LCM:
l = lcm(l, nums[j])
The incremental computation is key here - instead of recalculating from scratch for each subarray, we build upon the previous values.
- Update the product:
-
Check the product equivalent condition: If
p == g * l
, we found a valid subarray from indexi
toj
. Update the answer:ans = max(ans, j - i + 1)
-
Pruning optimization: If
p > max_p
, break from the inner loop. Since the product only increases as we extend the subarray, and it's already exceeded the maximum possible value oflcm * gcd
, no further extensions can be valid.
Why this works:
- The GCD of a subarray can only decrease or stay the same as we add more elements
- The LCM typically increases as we add more elements
- The product always increases (since all numbers are positive)
- The precomputed
max_p
gives us an upper bound because:- The LCM of any subarray ≤ LCM of the entire array
- The GCD of any subarray ≤ maximum element in the array
This ensures we explore all potentially valid subarrays while avoiding unnecessary computation when the product grows too large.
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 the solution with nums = [6, 12, 3]
.
Initial Setup:
n = 3
ans = 0
(tracks maximum length)max_p = lcm(6, 12, 3) * max(6, 12, 3) = 12 * 12 = 144
(upper bound for pruning)
Starting from index i = 0:
Subarray [6] (i=0, j=0):
p = 1 * 6 = 6
g = gcd(0, 6) = 6
l = lcm(1, 6) = 6
- Check:
6 == 6 * 6
? No (6 ≠ 36) - Check pruning:
6 > 144
? No, continue
Subarray [6, 12] (i=0, j=1):
p = 6 * 12 = 72
g = gcd(6, 12) = 6
l = lcm(6, 12) = 12
- Check:
72 == 6 * 12
? Yes! ✓ - Update:
ans = max(0, 1 - 0 + 1) = 2
- Check pruning:
72 > 144
? No, continue
Subarray [6, 12, 3] (i=0, j=2):
p = 72 * 3 = 216
g = gcd(6, 3) = 3
l = lcm(12, 3) = 12
- Check:
216 == 3 * 12
? No (216 ≠ 36) - Check pruning:
216 > 144
? Yes, break inner loop
Starting from index i = 1:
Subarray [12] (i=1, j=1):
p = 1 * 12 = 12
g = gcd(0, 12) = 12
l = lcm(1, 12) = 12
- Check:
12 == 12 * 12
? No (12 ≠ 144) - Check pruning:
12 > 144
? No, continue
Subarray [12, 3] (i=1, j=2):
p = 12 * 3 = 36
g = gcd(12, 3) = 3
l = lcm(12, 3) = 12
- Check:
36 == 3 * 12
? Yes! ✓ - Update:
ans = max(2, 2 - 1 + 1) = 2
- Check pruning:
36 > 144
? No, continue
Starting from index i = 2:
Subarray [3] (i=2, j=2):
p = 1 * 3 = 3
g = gcd(0, 3) = 3
l = lcm(1, 3) = 3
- Check:
3 == 3 * 3
? No (3 ≠ 9)
Final Result: The longest product equivalent subarray has length 2, corresponding to subarrays [6, 12] and [12, 3].
The key observations from this walkthrough:
- The incremental computation saves time - we build upon previous products, GCDs, and LCMs
- The pruning optimization (
p > max_p
) helps us skip impossible extensions early - The equation
prod = gcd * lcm
holds for special cases where the numbers have particular divisibility relationships (like 6 and 12, or 12 and 3)
Solution Implementation
1class Solution:
2 def maxLength(self, nums: List[int]) -> int:
3 """
4 Find the maximum length of a subarray where the product equals GCD * LCM.
5
6 For any subarray, the mathematical property holds that:
7 product of elements = GCD of elements * LCM of elements
8 if and only if the subarray contains coprime elements in a specific pattern.
9
10 Args:
11 nums: List of positive integers
12
13 Returns:
14 Maximum length of valid subarray
15 """
16 n = len(nums)
17 max_length = 0
18
19 # Calculate upper bound for product to avoid unnecessary computation
20 # If product exceeds LCM of all elements * max element, we can stop early
21 from math import gcd, lcm
22 max_product_bound = lcm(*nums) * max(nums)
23
24 # Try all possible subarrays starting from index i
25 for i in range(n):
26 current_product = 1
27 current_gcd = 0 # GCD with 0 returns the other number
28 current_lcm = 1
29
30 # Extend subarray from index i to j
31 for j in range(i, n):
32 # Update product, GCD, and LCM incrementally
33 current_product *= nums[j]
34 current_gcd = gcd(current_gcd, nums[j])
35 current_lcm = lcm(current_lcm, nums[j])
36
37 # Check if the mathematical property holds
38 if current_product == current_gcd * current_lcm:
39 max_length = max(max_length, j - i + 1)
40
41 # Early termination: if product is too large, no need to continue
42 if current_product > max_product_bound:
43 break
44
45 return max_length
46
1class Solution {
2 /**
3 * Finds the maximum length of a subarray where the product equals GCD * LCM.
4 *
5 * @param nums the input array of integers
6 * @return the maximum length of valid subarray
7 */
8 public int maxLength(int[] nums) {
9 // Calculate the maximum element and LCM of all elements
10 // These will be used to determine an upper bound for the product
11 int maxElement = 0;
12 int overallLcm = 1;
13
14 for (int num : nums) {
15 maxElement = Math.max(maxElement, num);
16 overallLcm = lcm(overallLcm, num);
17 }
18
19 // Calculate the maximum possible product to avoid overflow
20 // and optimize by early termination
21 int maxProduct = overallLcm * maxElement;
22
23 int n = nums.length;
24 int maxLength = 0;
25
26 // Try all possible subarrays starting from index i
27 for (int i = 0; i < n; ++i) {
28 int product = 1;
29 int currentGcd = 0;
30 int currentLcm = 1;
31
32 // Extend the subarray from i to j
33 for (int j = i; j < n; ++j) {
34 // Update product, GCD, and LCM for the current subarray
35 product *= nums[j];
36 currentGcd = gcd(currentGcd, nums[j]);
37 currentLcm = lcm(currentLcm, nums[j]);
38
39 // Check if the current subarray satisfies the condition
40 // product = GCD * LCM
41 if (product == currentGcd * currentLcm) {
42 maxLength = Math.max(maxLength, j - i + 1);
43 }
44
45 // Early termination if product exceeds the maximum bound
46 if (product > maxProduct) {
47 break;
48 }
49 }
50 }
51
52 return maxLength;
53 }
54
55 /**
56 * Calculates the Greatest Common Divisor (GCD) of two numbers
57 * using Euclidean algorithm.
58 *
59 * @param a first number
60 * @param b second number
61 * @return the GCD of a and b
62 */
63 private int gcd(int a, int b) {
64 while (b != 0) {
65 int temp = b;
66 b = a % b;
67 a = temp;
68 }
69 return a;
70 }
71
72 /**
73 * Calculates the Least Common Multiple (LCM) of two numbers
74 * using the formula: LCM(a,b) = a * b / GCD(a,b).
75 *
76 * @param a first number
77 * @param b second number
78 * @return the LCM of a and b
79 */
80 private int lcm(int a, int b) {
81 // Divide first to avoid potential overflow
82 return a / gcd(a, b) * b;
83 }
84}
85
1class Solution {
2public:
3 int maxLength(vector<int>& nums) {
4 // Find the maximum element and LCM of all elements in the array
5 // These will be used to establish an upper bound for pruning
6 int maxElement = 0;
7 int overallLcm = 1;
8 for (int num : nums) {
9 maxElement = max(maxElement, num);
10 overallLcm = lcm(overallLcm, num);
11 }
12
13 // Calculate the maximum possible product as a pruning threshold
14 // If any subarray's product exceeds this, it cannot satisfy the condition
15 long long maxProduct = (long long)overallLcm * maxElement;
16
17 int n = nums.size();
18 int maxSubarrayLength = 0;
19
20 // Try all possible subarrays starting from index i
21 for (int i = 0; i < n; ++i) {
22 long long product = 1; // Product of current subarray
23 long long gcdValue = 0; // GCD of current subarray
24 long long lcmValue = 1; // LCM of current subarray
25
26 // Extend the subarray ending at index j
27 for (int j = i; j < n; ++j) {
28 // Update product, GCD, and LCM for the subarray [i, j]
29 product *= nums[j];
30 gcdValue = gcd(gcdValue, nums[j]);
31 lcmValue = lcm(lcmValue, nums[j]);
32
33 // Check if the current subarray satisfies the condition:
34 // product of elements = GCD * LCM
35 if (product == gcdValue * lcmValue) {
36 maxSubarrayLength = max(maxSubarrayLength, j - i + 1);
37 }
38
39 // Prune: if product exceeds the maximum threshold,
40 // no need to extend further from this starting point
41 if (product > maxProduct) {
42 break;
43 }
44 }
45 }
46
47 return maxSubarrayLength;
48 }
49};
50
1function maxLength(nums: number[]): number {
2 // Find the maximum element and LCM of all elements in the array
3 // These will be used to establish an upper bound for pruning
4 let maxElement = 0;
5 let overallLcm = 1;
6 for (const num of nums) {
7 maxElement = Math.max(maxElement, num);
8 overallLcm = lcm(overallLcm, num);
9 }
10
11 // Calculate the maximum possible product as a pruning threshold
12 // If any subarray's product exceeds this, it cannot satisfy the condition
13 const maxProduct = overallLcm * maxElement;
14
15 const n = nums.length;
16 let maxSubarrayLength = 0;
17
18 // Try all possible subarrays starting from index i
19 for (let i = 0; i < n; i++) {
20 let product = 1; // Product of current subarray
21 let gcdValue = 0; // GCD of current subarray
22 let lcmValue = 1; // LCM of current subarray
23
24 // Extend the subarray ending at index j
25 for (let j = i; j < n; j++) {
26 // Update product, GCD, and LCM for the subarray [i, j]
27 product *= nums[j];
28 gcdValue = gcd(gcdValue, nums[j]);
29 lcmValue = lcm(lcmValue, nums[j]);
30
31 // Check if the current subarray satisfies the condition:
32 // product of elements = GCD * LCM
33 if (product === gcdValue * lcmValue) {
34 maxSubarrayLength = Math.max(maxSubarrayLength, j - i + 1);
35 }
36
37 // Prune: if product exceeds the maximum threshold,
38 // no need to extend further from this starting point
39 if (product > maxProduct) {
40 break;
41 }
42 }
43 }
44
45 return maxSubarrayLength;
46}
47
48// Helper function to calculate the greatest common divisor (GCD)
49function gcd(a: number, b: number): number {
50 // Handle edge case where one number is 0
51 if (a === 0) return b;
52 if (b === 0) return a;
53
54 // Euclidean algorithm for GCD
55 while (b !== 0) {
56 const temp = b;
57 b = a % b;
58 a = temp;
59 }
60 return a;
61}
62
63// Helper function to calculate the least common multiple (LCM)
64function lcm(a: number, b: number): number {
65 // LCM formula: LCM(a, b) = (a * b) / GCD(a, b)
66 // Handle edge case to avoid division by zero
67 if (a === 0 || b === 0) return 0;
68 return Math.floor((a * b) / gcd(a, b));
69}
70
Time and Space Complexity
Time Complexity: O(n² × (log M + k))
where n
is the length of the array, M
is the maximum value in the array, and k
is the average number of elements in each subarray.
- The outer loop runs
n
times for each starting positioni
- The inner loop runs at most
n
times for each ending positionj
- For each iteration of the inner loop:
- Product calculation:
O(1)
- GCD calculation:
O(log M)
where M is the maximum value - LCM calculation:
O(log M)
- Product calculation:
- The initial LCM calculation
lcm(*nums)
takesO(n × log M)
time - The pruning condition
if p > max_p: break
helps reduce the actual iterations, but worst-case remainsO(n²)
Overall: O(n × log M) + O(n² × log M) = O(n² × log M)
Space Complexity: O(1)
- The algorithm uses only a constant amount of extra space for variables:
ans
,max_p
,p
,g
,l
,i
,j
- The
gcd
andlcm
functions useO(1)
space (iterative implementation) - The initial
lcm(*nums)
calculation might useO(n)
space temporarily for the unpacking operation, but this depends on the implementation - No additional data structures are used
Overall space complexity: O(1)
auxiliary space (not counting the input array)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow with Large Products
The most critical pitfall is that the product of array elements can grow exponentially large. For example, with an array like [10, 20, 30, 40, 50]
, the product quickly exceeds typical integer limits. In Python, while integers can be arbitrarily large, the computation becomes inefficient and the comparison p == g * l
might fail due to numerical issues in other languages.
Solution:
- Use the pruning optimization with
max_product_bound
to stop early when products become too large - Consider using logarithms for comparison if dealing with very large numbers:
log(p) ≈ log(g) + log(l)
- In languages with fixed integer sizes, use appropriate data types (e.g.,
long long
in C++)
2. Incorrect GCD Initialization
A common mistake is initializing current_gcd = nums[i]
at the start of each subarray. This seems logical but breaks the incremental computation pattern and requires special handling for single-element subarrays.
Solution:
Initialize current_gcd = 0
. The mathematical property gcd(0, x) = x
for any positive integer x
makes this work seamlessly for all subarray sizes.
3. Computing LCM of Large Numbers
The LCM can grow very quickly, especially with numbers that share few common factors. Computing lcm(a, b)
using the formula (a * b) / gcd(a, b)
can cause overflow even in intermediate calculations.
Solution:
- Use the built-in
lcm
function which handles this properly - If implementing manually, compute as
a // gcd(a, b) * b
to avoid overflow - For multiple numbers, compute LCM incrementally rather than all at once
4. Inefficient Max Product Bound Calculation
Computing lcm(*nums)
for the entire array can be extremely expensive or cause overflow when the array is large or contains many coprime numbers.
Solution:
# Instead of lcm(*nums) * max(nums), use a more conservative bound:
max_product_bound = float('inf') # Or use a reasonable upper limit
# Alternatively, compute it incrementally with early stopping:
temp_lcm = 1
for num in nums:
temp_lcm = lcm(temp_lcm, num)
if temp_lcm > 10**18: # Some reasonable threshold
max_product_bound = float('inf')
break
else:
max_product_bound = temp_lcm * max(nums)
5. Missing Edge Cases
Not handling arrays with single elements or when all elements are identical can lead to incorrect results.
Solution:
- Single element subarrays always satisfy the property (since
x = gcd(x) * lcm(x) = x * x / x = x
) - Arrays where all elements are the same number
k
satisfy the property for any length (sincek^n = k * k^(n-1)
) - Ensure the algorithm handles these cases naturally through the incremental computation
You are given an array of intervals where intervals[i] = [start_i, end_i]
represent the start and end of the ith
interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.
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
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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!