3574. Maximize Subarray GCD Score
Problem Description
You have an array of positive integers nums
and an integer k
.
You can perform up to k
operations on the array. In each operation, you select one element and double its value. However, each element can only be doubled once throughout all operations.
After modifying the array (or keeping it unchanged), you need to find a contiguous subarray that maximizes a specific score. The score of a subarray is calculated as:
score = length of subarray × GCD of all elements in the subarray
Where GCD (Greatest Common Divisor) is the largest integer that evenly divides all elements in the subarray.
Your goal is to return the maximum possible score that can be achieved by:
- First, strategically doubling up to
k
elements (each at most once) - Then, selecting the contiguous subarray that gives the highest score
For example, if you have nums = [2, 3, 6]
and k = 1
, you might double one element to get [4, 3, 6]
or [2, 6, 6]
or [2, 3, 12]
, then find the subarray with the maximum score. The subarray could be a single element, multiple consecutive elements, or the entire array.
The key insight is that doubling elements can potentially increase the GCD of subarrays containing them, which in turn increases the score. You need to determine which elements to double and which subarray to select to maximize the final score.
Intuition
Since the array length is limited to n ≤ 1500
, we can afford to check all possible contiguous subarrays - there are O(n²)
of them. This brute force enumeration is feasible given the constraints.
The key observation is about how doubling affects GCD. When we double a number, we're multiplying it by 2, which means we're adding one more factor of 2 to it. For a subarray's GCD to benefit from doubling operations, the GCD itself must be able to increase by a factor of 2.
Here's the critical insight: The GCD of a set of numbers can only have as many factors of 2 as the number with the fewest factors of 2 in that set. For example, if we have [4, 8, 12]
where:
4 = 2² × 1
(2 factors of 2)8 = 2³ × 1
(3 factors of 2)12 = 2² × 3
(2 factors of 2)
The GCD is limited by the numbers with only 2 factors of 2, so GCD = 4
.
If we double some elements, we add one factor of 2 to them. To maximize the GCD, we should double the elements that have the minimum number of factors of 2 in our chosen subarray. If we have k
operations and there are exactly k
or fewer elements with the minimum factor count, we can increase all of them by one factor of 2, effectively doubling the GCD.
This leads to our strategy:
- Precompute how many factors of 2 each element has
- For each possible subarray
[l, r]
:- Calculate the base GCD of original elements
- Find the minimum factor of 2 count among elements in this subarray
- Count how many elements have this minimum
- If this count ≤
k
, we can double all of them, so the final GCD isbase_GCD × 2
- Otherwise, the GCD stays as
base_GCD
- Calculate score as
GCD × subarray_length
This approach ensures we optimally use our doubling operations for each subarray we consider.
Learn more about Math patterns.
Solution Approach
The implementation follows the intuition by breaking down the problem into manageable steps:
Step 1: Preprocess Factor of 2 Counts
First, we create an array cnt
to store how many times 2 divides each element:
cnt = [0] * n
for i, x in enumerate(nums):
while x % 2 == 0:
cnt[i] += 1
x //= 2
For each number, we repeatedly divide by 2 until it's odd, counting the divisions. For example, 12 = 2² × 3
, so cnt[i] = 2
.
Step 2: Enumerate All Subarrays
We use nested loops where l
is the left boundary and r
is the right boundary:
for l in range(n):
g = 0 # GCD accumulator
mi = inf # Minimum factor of 2 count
t = 0 # Count of elements with minimum factors
for r in range(l, n):
# Process subarray [l, r]
Step 3: Maintain Subarray Properties
For each subarray [l, r]
, we maintain three key variables:
g
: The running GCD of elements in the current subarray. We usegcd(g, nums[r])
to incrementally update it as we extend the subarray.mi
: The minimum number of factors of 2 among all elements in the current subarray.t
: The count of elements that have exactlymi
factors of 2.
g = gcd(g, nums[r]) if cnt[r] < mi: mi = cnt[r] # Found a new minimum t = 1 # Reset count to 1 elif cnt[r] == mi: t += 1 # Another element with minimum factors
Step 4: Calculate Score with Optimal Doubling
The key decision is whether we can double the GCD:
ans = max(ans, (g if t > k else g * 2) * (r - l + 1))
- If
t > k
: We have more elements with minimum factors than operations available. We can't double all of them, so the GCD remainsg
. - If
t ≤ k
: We can double all elements with minimum factors, effectively adding one factor of 2 to the GCD, making itg * 2
.
The score is then GCD × length
, where length is (r - l + 1)
.
Time Complexity: O(n² × log(max(nums)))
- We enumerate O(n²)
subarrays, and for each, we compute GCD which takes O(log(max(nums)))
time.
Space Complexity: O(n)
for the cnt
array storing factor counts.
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 work through a concrete example with nums = [6, 12, 8]
and k = 2
.
Step 1: Count factors of 2 for each element
6 = 2¹ × 3
→cnt[0] = 1
(one factor of 2)12 = 2² × 3
→cnt[1] = 2
(two factors of 2)8 = 2³ × 1
→cnt[2] = 3
(three factors of 2)
So cnt = [1, 2, 3]
Step 2: Enumerate all subarrays and calculate scores
Let's trace through each subarray:
Subarray [0, 0]: just [6]
- Base GCD = 6
- Minimum factors of 2:
mi = 1
- Count with minimum:
t = 1
- Since
t = 1 ≤ k = 2
, we can double the GCD - Score =
(6 × 2) × 1 = 12
Subarray [0, 1]: [6, 12]
- Base GCD = gcd(6, 12) = 6
- Minimum factors of 2:
mi = 1
(from element 6) - Count with minimum:
t = 1
(only element 6 has 1 factor) - Since
t = 1 ≤ k = 2
, we can double the GCD - Score =
(6 × 2) × 2 = 24
Subarray [0, 2]: [6, 12, 8]
- Base GCD = gcd(gcd(6, 12), 8) = gcd(6, 8) = 2
- Minimum factors of 2:
mi = 1
(from element 6) - Count with minimum:
t = 1
(only element 6) - Since
t = 1 ≤ k = 2
, we can double the GCD - Score =
(2 × 2) × 3 = 12
Subarray [1, 1]: just [12]
- Base GCD = 12
- Minimum factors of 2:
mi = 2
- Count with minimum:
t = 1
- Since
t = 1 ≤ k = 2
, we can double the GCD - Score =
(12 × 2) × 1 = 24
Subarray [1, 2]: [12, 8]
- Base GCD = gcd(12, 8) = 4
- Minimum factors of 2:
mi = 2
(from element 12) - Count with minimum:
t = 1
(only element 12 has 2 factors) - Since
t = 1 ≤ k = 2
, we can double the GCD - Score =
(4 × 2) × 2 = 16
Subarray [2, 2]: just [8]
- Base GCD = 8
- Minimum factors of 2:
mi = 3
- Count with minimum:
t = 1
- Since
t = 1 ≤ k = 2
, we can double the GCD - Score =
(8 × 2) × 1 = 16
Maximum score = 24 (achieved by either subarray [0, 1] or [1, 1])
The optimal strategy for subarray [0, 1] would be to double element 6 (which has the minimum factors of 2), transforming [6, 12]
to [12, 12]
with GCD = 12 and score = 12 × 2 = 24.
Solution Implementation
1from typing import List
2from math import gcd, inf
3
4
5class Solution:
6 def maxGCDScore(self, nums: List[int], k: int) -> int:
7 """
8 Calculate the maximum GCD score for subarrays of nums.
9 The score is GCD * subarray_length, with potential doubling based on factors of 2.
10
11 Args:
12 nums: List of integers
13 k: Threshold for determining whether to double the GCD
14
15 Returns:
16 Maximum possible score
17 """
18 n = len(nums)
19
20 # Count the number of factors of 2 in each number
21 factors_of_two = [0] * n
22 for i, num in enumerate(nums):
23 while num % 2 == 0:
24 factors_of_two[i] += 1
25 num //= 2
26
27 max_score = 0
28
29 # Try all possible subarrays starting from index left
30 for left in range(n):
31 current_gcd = 0
32 min_factor_count = inf
33 elements_with_min_factors = 0
34
35 # Extend the subarray to index right
36 for right in range(left, n):
37 # Update GCD of current subarray
38 current_gcd = gcd(current_gcd, nums[right])
39
40 # Track minimum factor of 2 count and how many elements have it
41 if factors_of_two[right] < min_factor_count:
42 min_factor_count = factors_of_two[right]
43 elements_with_min_factors = 1
44 elif factors_of_two[right] == min_factor_count:
45 elements_with_min_factors += 1
46
47 # Calculate score: double GCD if elements with minimum factors exceed k
48 subarray_length = right - left + 1
49 if elements_with_min_factors > k:
50 score = current_gcd * subarray_length
51 else:
52 score = current_gcd * 2 * subarray_length
53
54 max_score = max(max_score, score)
55
56 return max_score
57
1class Solution {
2 public long maxGCDScore(int[] nums, int k) {
3 int n = nums.length;
4
5 // Count the number of factors of 2 in each element
6 int[] factorsOfTwo = new int[n];
7 for (int i = 0; i < n; ++i) {
8 int currentNum = nums[i];
9 // Count how many times the number can be divided by 2
10 while (currentNum % 2 == 0) {
11 factorsOfTwo[i]++;
12 currentNum /= 2;
13 }
14 }
15
16 long maxScore = 0;
17
18 // Try all possible subarrays starting from index left
19 for (int left = 0; left < n; ++left) {
20 int currentGCD = 0;
21 int minFactorsOfTwo = 1 << 30; // Initialize with a large value
22 int countWithMinFactors = 0; // Count of elements with minimum factors of 2
23
24 // Extend the subarray to the right
25 for (int right = left; right < n; ++right) {
26 // Update GCD of current subarray
27 currentGCD = gcd(currentGCD, nums[right]);
28
29 // Track the minimum number of factors of 2 and count of elements with that minimum
30 if (factorsOfTwo[right] < minFactorsOfTwo) {
31 minFactorsOfTwo = factorsOfTwo[right];
32 countWithMinFactors = 1;
33 } else if (factorsOfTwo[right] == minFactorsOfTwo) {
34 countWithMinFactors++;
35 }
36
37 // Calculate score: length * (GCD or GCD*2 based on condition)
38 long subarrayLength = right - left + 1L;
39 long gcdValue = (countWithMinFactors > k) ? currentGCD : currentGCD * 2;
40 maxScore = Math.max(maxScore, subarrayLength * gcdValue);
41 }
42 }
43
44 return maxScore;
45 }
46
47 /**
48 * Calculates the greatest common divisor using Euclidean algorithm
49 * @param a first number
50 * @param b second number
51 * @return GCD of a and b
52 */
53 private int gcd(int a, int b) {
54 return b == 0 ? a : gcd(b, a % b);
55 }
56}
57
1class Solution {
2public:
3 long long maxGCDScore(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // Count trailing zeros (number of times each element is divisible by 2)
7 vector<int> trailingZeros(n);
8 for (int i = 0; i < n; ++i) {
9 int value = nums[i];
10 while (value % 2 == 0) {
11 ++trailingZeros[i];
12 value /= 2;
13 }
14 }
15
16 long long maxScore = 0;
17
18 // Try all possible subarrays starting from index left
19 for (int left = 0; left < n; ++left) {
20 int currentGCD = 0;
21 int minTrailingZeros = INT32_MAX;
22 int countWithMinZeros = 0;
23
24 // Extend the subarray to index right
25 for (int right = left; right < n; ++right) {
26 // Update GCD of current subarray
27 currentGCD = gcd(currentGCD, nums[right]);
28
29 // Track elements with minimum trailing zeros
30 if (trailingZeros[right] < minTrailingZeros) {
31 minTrailingZeros = trailingZeros[right];
32 countWithMinZeros = 1;
33 } else if (trailingZeros[right] == minTrailingZeros) {
34 ++countWithMinZeros;
35 }
36
37 // Calculate score for current subarray
38 // If count of elements with min trailing zeros > k, use GCD as is
39 // Otherwise, we can double the GCD (multiply by 2)
40 int subarrayLength = right - left + 1;
41 long long finalGCD = (countWithMinZeros > k) ? currentGCD : currentGCD * 2;
42 long long score = static_cast<long long>(subarrayLength) * finalGCD;
43
44 // Update maximum score
45 maxScore = max(maxScore, score);
46 }
47 }
48
49 return maxScore;
50 }
51};
52
1/**
2 * Calculates the maximum GCD score for subarrays with special doubling rule
3 * @param nums - Array of positive integers
4 * @param k - Maximum number of elements that can be doubled
5 * @returns Maximum possible GCD score
6 */
7function maxGCDScore(nums: number[], k: number): number {
8 const n: number = nums.length;
9 // Array to store the count of factors of 2 for each number
10 const factorsOfTwo: number[] = Array(n).fill(0);
11
12 // Count how many times each number can be divided by 2
13 for (let i = 0; i < n; ++i) {
14 let currentNum: number = nums[i];
15 while (currentNum % 2 === 0) {
16 factorsOfTwo[i]++;
17 currentNum /= 2;
18 }
19 }
20
21 let maxScore: number = 0;
22
23 // Try all possible subarrays
24 for (let left = 0; left < n; ++left) {
25 let currentGcd: number = 0;
26 let minFactorCount: number = Number.MAX_SAFE_INTEGER;
27 let elementsWithMinFactors: number = 0;
28
29 // Extend the subarray to the right
30 for (let right = left; right < n; ++right) {
31 // Update GCD for current subarray
32 currentGcd = gcd(currentGcd, nums[right]);
33
34 // Track elements with minimum factor of 2 count
35 if (factorsOfTwo[right] < minFactorCount) {
36 minFactorCount = factorsOfTwo[right];
37 elementsWithMinFactors = 1;
38 } else if (factorsOfTwo[right] === minFactorCount) {
39 elementsWithMinFactors++;
40 }
41
42 // Calculate subarray length
43 const subarrayLength: number = right - left + 1;
44
45 // Calculate score: double GCD if elements with min factors <= k
46 const score: number = (elementsWithMinFactors > k ? currentGcd : currentGcd * 2) * subarrayLength;
47
48 // Update maximum score
49 maxScore = Math.max(maxScore, score);
50 }
51 }
52
53 return maxScore;
54}
55
56/**
57 * Calculates the Greatest Common Divisor of two numbers using Euclidean algorithm
58 * @param a - First number
59 * @param b - Second number
60 * @returns GCD of a and b
61 */
62function gcd(a: number, b: number): number {
63 while (b !== 0) {
64 const remainder: number = b;
65 b = a % b;
66 a = remainder;
67 }
68 return a;
69}
70
Time and Space Complexity
Time Complexity: O(n² × log n)
The algorithm consists of:
- A preprocessing loop that counts trailing zeros (factors of 2) for each element, which takes
O(n × log max(nums))
time, where each number is divided by 2 until odd. - A nested loop structure where:
- The outer loop runs
n
times (for each starting positionl
) - The inner loop runs up to
n
times (for each ending positionr
froml
ton-1
) - Inside the inner loop, the
gcd(g, nums[r])
operation takesO(log min(g, nums[r]))
time - Since we're computing GCD iteratively and the value of
g
can be at mostmax(nums)
, each GCD operation is bounded byO(log max(nums))
- The outer loop runs
The dominant operation is the nested loops with GCD computation, giving us O(n² × log n)
time complexity, where n
is the length of the array and the logarithmic factor comes from the GCD operations.
Space Complexity: O(n)
The algorithm uses:
- An array
cnt
of sizen
to store the count of factors of 2 for each element:O(n)
- A few constant variables (
ans
,l
,g
,mi
,t
,r
):O(1)
Therefore, the total space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Problem Constraints
The Issue: The solution assumes we can only double the GCD by doubling elements with the minimum number of factors of 2, but the problem states we can double ANY k elements (each at most once). The current approach doesn't consider all possible combinations of which k elements to double.
Example Where It Fails:
nums = [3, 6, 12]
,k = 2
- Current approach: Counts factors of 2 as
[0, 1, 2]
- For subarray
[6, 12]
: GCD = 6, min factors = 1, count = 1 - Since count (1) ≤ k (2), it doubles GCD to 12, giving score = 12 × 2 = 24
- But: We could double both 3 and 6 to get
[6, 12, 12]
, then subarray[12, 12]
has GCD = 12, score = 12 × 2 = 24 - Or double 3 and 12 to get
[6, 6, 24]
, subarray[6, 6]
has GCD = 6, score = 6 × 2 = 12
The real issue is that we need to consider which specific k elements to double to maximize the score, not just check if we can double all elements with minimum factors.
Pitfall 2: Incorrect GCD Doubling Logic
The Issue: The code doubles the GCD when elements_with_min_factors ≤ k
, but this doesn't correctly model the problem. We can't simply "double the GCD" - we need to actually double specific elements and then recalculate the GCD.
Correct Approach:
def maxGCDScore(self, nums: List[int], k: int) -> int:
n = len(nums)
max_score = 0
# Generate all possible states with up to k elements doubled
# Using bit manipulation to represent which elements are doubled
for mask in range(1 << n):
if bin(mask).count('1') > k:
continue
# Create modified array based on mask
modified = nums[:]
for i in range(n):
if mask & (1 << i):
modified[i] *= 2
# Find best subarray in this modified array
for left in range(n):
current_gcd = 0
for right in range(left, n):
current_gcd = gcd(current_gcd, modified[right])
length = right - left + 1
score = current_gcd * length
max_score = max(max_score, score)
return max_score
Pitfall 3: Efficiency Concerns
The Issue: The corrected approach has exponential time complexity O(2^n × n² × log(max_val)) which becomes impractical for large arrays.
Optimization Strategy: For larger inputs, consider:
- Pruning: Skip masks that can't improve the current best score
- Dynamic Programming: Cache GCD calculations for repeated subarrays
- Heuristics: Focus on doubling elements that appear in high-scoring subarrays first
def maxGCDScore(self, nums: List[int], k: int) -> int:
n = len(nums)
if n <= 15: # Use brute force for small arrays
return self.bruteForce(nums, k)
else: # Use heuristic approach for larger arrays
return self.heuristicApproach(nums, k)
The original solution's approach of counting factors of 2 is an interesting optimization attempt, but it makes incorrect assumptions about how doubling operations affect the GCD of subarrays.
Which of these properties could exist for a graph but not a tree?
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
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!