Facebook Pixel

3574. Maximize Subarray GCD Score

HardArrayMathEnumerationNumber Theory
Leetcode Link

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:

  1. First, strategically doubling up to k elements (each at most once)
  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Precompute how many factors of 2 each element has
  2. 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 is base_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 use gcd(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 exactly mi 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 remains g.
  • If t ≤ k: We can double all elements with minimum factors, effectively adding one factor of 2 to the GCD, making it g * 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 Evaluator

Example 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¹ × 3cnt[0] = 1 (one factor of 2)
  • 12 = 2² × 3cnt[1] = 2 (two factors of 2)
  • 8 = 2³ × 1cnt[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 position l)
    • The inner loop runs up to n times (for each ending position r from l to n-1)
    • Inside the inner loop, the gcd(g, nums[r]) operation takes O(log min(g, nums[r])) time
    • Since we're computing GCD iteratively and the value of g can be at most max(nums), each GCD operation is bounded by O(log max(nums))

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 size n 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:

  1. Pruning: Skip masks that can't improve the current best score
  2. Dynamic Programming: Cache GCD calculations for repeated subarrays
  3. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More