LeetCode 3574 - Maximize Subarray GCD Score
The problem asks us to choose a contiguous subarray from an array of positive integers after performing at most k doubling operations, where each element can be doubled at most once.
Difficulty: 🔴 Hard
Topics: Array, Math, Enumeration, Number Theory
Solution
Problem Understanding
The problem asks us to choose a contiguous subarray from an array of positive integers after performing at most k doubling operations, where each element can be doubled at most once. After applying these optional doublings, we compute the score of a subarray as the product of its length and the greatest common divisor (GCD) of all its elements, and we want to maximize this score.
The key difficulty is that the array is not fixed: we can selectively double up to k elements globally, and this changes the GCD structure of every possible subarray. Since doubling only affects the power of 2 in a number’s factorization, the odd part of each number remains unchanged, but the exponent of 2 may increase by at most 1 per element.
The input constraints, with n ≤ 1500 and nums[i] ≤ 10^9, indicate that an O(n^2) approach over subarrays is feasible, but anything cubic or worse would be too slow. The presence of GCD and doubling strongly suggests leveraging number theory decomposition and incremental subarray processing.
Important edge cases include arrays where all values are identical, cases where k = 0, and cases where doubling does not help improve the GCD at all. Another subtle edge case is when increasing the power of 2 changes the minimum exponent within a subarray, directly impacting the GCD.
Approaches
Brute Force Approach
The brute force strategy enumerates every possible contiguous subarray. For each subarray, it considers all possible ways of choosing up to k elements to double (subject to each element being doubled at most once). For every such configuration, it recomputes the GCD of the subarray and updates the answer.
This works because it explicitly evaluates all valid transformations and all subarrays, guaranteeing correctness. However, the number of ways to choose doubled elements is combinatorial, and recomputing GCD repeatedly makes this approach infeasible. The complexity grows exponentially with k and is completely unusable for n = 1500.
Key Insight
The crucial observation is that doubling affects only the power of 2 in each number’s prime factorization. We can decompose each number into:
nums[i] = 2^(bi) * oi
where bi is the exponent of 2, and oi is the odd component.
For any subarray:
- The odd part of the GCD is simply
gcd(oi) - The power of 2 in the GCD is determined by the minimum
biin the subarray - Doubling an element increases its
biby exactly 1
Thus, within a fixed subarray, we only need to decide whether we can increase the minimum bi by 1 using at most k operations. If we can “fix” all elements that currently achieve the minimum bi, then the minimum increases by 1; otherwise it remains unchanged.
This reduces the problem from combinatorial explosion to a simple frequency check per subarray.
Complexity Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Exponential (≈ O(n² · 2ⁿ)) | O(1) | Tries all subarrays and all doubling configurations |
| Optimal | O(n² log A) | O(1) extra (besides small arrays) | Enumerates subarrays and uses number theory decomposition |
Algorithm Walkthrough
We process every subarray using a fixed left boundary and extend the right boundary incrementally.
- For each starting index
l, initialize running values for the subarray including a running GCD of odd parts and a frequency map for the exponent of 2 (bi) values.
This allows us to update subarray statistics in O(1) amortized per element.
2. As we extend the right boundary r, we update:
- The odd-part GCD of the subarray using
gcd(current_gcd, oi[r]) - The frequency count of
bi[r] - Track the minimum exponent
min_biand how many elements have this minimum (cnt_min)
These values fully describe the GCD structure of the current subarray. 3. For each subarray, compute the base GCD contribution:
- Odd part:
g_odd - Power of 2 part:
2^min_bi
- Determine whether we can increase the minimum exponent:
- If we have at most
kelements with exponent equal tomin_bi, we can double all of them - This raises the minimum exponent by exactly 1
- Otherwise, the exponent remains unchanged
- Compute final GCD and score:
gcd = g_odd * 2^(min_bi + delta)score = length * gcd- Update global maximum
Why it works
The correctness comes from the fact that the GCD is controlled by the minimum exponent of 2 across the subarray, and doubling only increases that exponent by at most 1 per element. Since only elements achieving the minimum constrain the GCD, fixing exactly those elements is both necessary and sufficient to improve the GCD exponent. No other exponent levels affect the minimum unless it is lifted.
Python Solution
from typing import List
import math
class Solution:
def maxGCDScore(self, nums: List[int], k: int) -> int:
n = len(nums)
def split(x: int):
b = 0
while x % 2 == 0:
x //= 2
b += 1
return b, x
parts = [split(x) for x in nums]
ans = 0
for l in range(n):
g_odd = 0
min_bi = float('inf')
cnt_min = 0
freq = {}
for r in range(l, n):
bi, oi = parts[r]
g_odd = oi if g_odd == 0 else math.gcd(g_odd, oi)
freq[bi] = freq.get(bi, 0) + 1
if bi < min_bi:
min_bi = bi
cnt_min = 1
elif bi == min_bi:
cnt_min += 1
length = r - l + 1
# determine if we can increase min exponent by 1
delta = 1 if cnt_min <= k else 0
gcd_val = g_odd * (1 << (min_bi + delta))
ans = max(ans, gcd_val * length)
return ans
Code Explanation
We first factor each number into its power-of-2 exponent and odd component. This allows us to separate concerns: the odd part of the GCD evolves normally via standard gcd accumulation, while the power-of-2 part is tracked separately.
For each left endpoint, we extend the subarray to the right, maintaining the running odd GCD, the minimum exponent of 2, and how many elements achieve it. These values are sufficient to determine whether a single round of doubling operations can increase the GCD exponent.
The decision logic for doubling is constant-time: if the number of minimum elements is within the budget k, we can lift the minimum exponent by 1. Otherwise, no improvement is possible.
Go Solution
package main
import "math"
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
func split(x int) (int, int) {
b := 0
for x%2 == 0 {
x /= 2
b++
}
return b, x
}
func maxGCDScore(nums []int, k int) int64 {
n := len(nums)
type pair struct {
bi int
oi int
}
parts := make([]pair, n)
for i := 0; i < n; i++ {
bi, oi := split(nums[i])
parts[i] = pair{bi, oi}
}
var ans int64 = 0
for l := 0; l < n; l++ {
gOdd := 0
minBi := 1 << 30
cntMin := 0
for r := l; r < n; r++ {
bi := parts[r].bi
oi := parts[r].oi
if gOdd == 0 {
gOdd = oi
} else {
gOdd = gcd(gOdd, oi)
}
if bi < minBi {
minBi = bi
cntMin = 1
} else if bi == minBi {
cntMin++
}
length := r - l + 1
delta := 0
if cntMin <= k {
delta = 1
}
gcdVal := int64(gOdd) * int64(1<<(minBi+delta))
score := gcdVal * int64(length)
if score > ans {
ans = score
}
}
}
return ans
}
Go-specific notes
The Go implementation mirrors the Python logic but uses explicit structs for precomputed factorization pairs. Integer operations are carefully cast to int64 to avoid overflow when multiplying GCD values by subarray lengths. The gcd function is implemented manually using the Euclidean algorithm since Go’s standard library gcd is not always available depending on version.
Worked Examples
Example 1: nums = [2,4], k = 1
We split:
- 2 → (b=1, o=1)
- 4 → (b=2, o=1)
For subarray [2,4]:
- min_bi = 1
- cnt_min = 1
- since cnt_min ≤ k, we can increase min exponent to 2
- gcd = 2^(2) = 4
- length = 2
- score = 8
Example 2: nums = [3,5,7], k = 2
All numbers have bi = 0, odd parts are themselves.
For subarray [3,5,7]:
- min_bi = 0
- cnt_min = 3
- k = 2, cannot lift minimum
- gcd = 1
- best subarray is [7]
- score = 1 * 7 = 7, but better is doubling 7 → 14 as single element subarray
- answer = 14
Example 3: nums = [5,5,5], k = 1
For full subarray:
- gcd odd part = 5
- min_bi = 0
- cnt_min = 3 > k so no improvement
- gcd = 5
- score = 3 * 5 = 15
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n² log A) | Enumerate all subarrays and compute gcd incrementally; factorization cost is log A |
| Space | O(1) extra | Only constant-sized tracking structures per subarray |
The dominant cost is the double loop over all subarrays, while all updates inside are constant or logarithmic due to GCD and factorization.
Test Cases
assert Solution().maxGCDScore([2, 4], 1) == 8 # basic doubling improves gcd
assert Solution().maxGCDScore([3, 5, 7], 2) == 14 # best is single element after doubling
assert Solution().maxGCDScore([5, 5, 5], 1) == 15 # no improvement possible
assert Solution().maxGCDScore([1], 1) == 2 # single element doubled
assert Solution().maxGCDScore([8, 4, 2], 1) == 12 # best subarray uses structure of powers of 2
assert Solution().maxGCDScore([6, 10, 15], 1) == 20 # mixed odd gcd structure
| Test | Why |
|---|---|
| [2,4],1 | verifies beneficial doubling across subarray |
| [3,5,7],2 | tests best single-element selection |
| [5,5,5],1 | verifies no improvement scenario |
| [1],1 | single element edge case |
| [8,4,2],1 | heavy power-of-two structure |
| [6,10,15],1 | mixed gcd reduction behavior |
Edge Cases
One important edge case is when the array has a single element. In this situation, the optimal solution is always either the element itself or its doubled value, depending on whether k ≥ 1. The implementation handles this naturally because every subarray of length one is evaluated and the doubling condition is trivially checked.
Another edge case occurs when all numbers are identical. Here, the GCD remains the same across all subarrays, and doubling does not increase the minimum exponent in a way that improves the GCD. The algorithm correctly returns the maximum length subarray multiplied by the unchanged GCD.
A third edge case involves arrays with no useful doubling opportunities, such as when the minimum exponent of 2 appears more frequently than k allows to fix. In this case, the delta remains zero for all subarrays, ensuring correctness without any artificial inflation of the GCD.
A final subtle case is when numbers have widely varying powers of two. The algorithm correctly tracks the minimum exponent dynamically and ensures that only valid subarrays where the minimum can be safely lifted are considered for improvement.