LeetCode 2941 - Maximum GCD-Sum of a Subarray

The problem asks us to find the maximum gcd-sum of a subarray of a given integer array nums with the constraint that the subarray has at least k elements.

LeetCode Problem 2941

Difficulty: 🔴 Hard
Topics: Array, Math, Binary Search, Number Theory

Solution

Problem Understanding

The problem asks us to find the maximum gcd-sum of a subarray of a given integer array nums with the constraint that the subarray has at least k elements. The gcd-sum of a subarray is defined as the product of the sum of its elements and the greatest common divisor (GCD) of its elements. In other words, if a subarray a has elements [a_1, a_2, ..., a_m], then its gcd-sum is:

gcd_sum(a) = (a_1 + a_2 + ... + a_m) * gcd(a_1, a_2, ..., a_m)

The input consists of an array nums of length up to 10^5 and integer values up to 10^6, so naive solutions that consider all subarrays will be too slow. The output should be a single integer representing the maximum gcd-sum among all valid subarrays of length at least k.

Key observations include recognizing that subarrays must be contiguous, and that the GCD function has the property that for any subarray, gcd(a, b, c) <= min(a, b, c). This observation allows us to reason about potential values of GCD in an efficient way.

Edge cases to consider are subarrays where all elements are the same, where k = 1 (single elements are allowed), and where elements are prime or coprime with each other, which affects the GCD computation.

Approaches

Brute Force

The brute-force approach is straightforward but inefficient. We can iterate over all possible subarrays of length at least k, compute their sum and GCD, and then calculate the gcd-sum. This guarantees correctness because it evaluates all possible subarrays. However, the complexity is prohibitive because there are O(n^2) subarrays and computing the GCD of each subarray takes O(m) for a subarray of length m. Therefore, the total time complexity is roughly O(n^3), which is unacceptable for n = 10^5.

Optimal Approach

The key insight for a faster solution is to fix a candidate GCD and then find the maximum sum of a contiguous subarray whose elements are divisible by that GCD. This converts the problem into a variant of the maximum subarray sum problem with an additional divisibility constraint.

Steps to optimize:

  1. Precompute all divisors for each number up to 10^6.
  2. Iterate over candidate GCDs in decreasing order. For each GCD g, transform the array into values divisible by g (keep as is) or non-divisible (set to 0 or ignore).
  3. Use a sliding window or prefix sum approach to find the maximum sum of contiguous subarrays of length at least k where all elements are divisible by g.
  4. Multiply the candidate GCD g with the maximum subarray sum to get the gcd-sum for that candidate.
  5. Track the maximum across all candidate GCDs.

This approach reduces the problem from considering all subarrays and GCDs independently to iterating over potential GCDs (up to 10^6) and then performing a linear scan per candidate.

Approach Time Complexity Space Complexity Notes
Brute Force O(n^3) O(1) Compute sum and GCD for all subarrays of length >= k
Optimal O(n * log(max_num) * num_divisors) O(n) Iterate over candidate GCDs and use sliding window for max sum

Algorithm Walkthrough

  1. Identify the maximum value in the array nums to limit candidate GCDs.

  2. Initialize a variable max_gcd_sum to 0.

  3. For each possible GCD g from 1 to max value:

  4. Transform the array such that each element is either its value if divisible by g or 0 if not.

  5. Use a sliding window to find the maximum sum of contiguous elements of length at least k that are divisible by g.

  6. Compute the gcd-sum as g * max_sum.

  7. Update max_gcd_sum if this gcd-sum is larger.

  8. Return max_gcd_sum.

Why it works: By iterating over all possible GCDs and using a sliding window on valid subarrays, we ensure that every potential gcd-sum is considered. Since larger GCDs are considered first, the algorithm finds the optimal gcd-sum efficiently.

Python Solution

from math import gcd
from typing import List

class Solution:
    def maxGcdSum(self, nums: List[int], k: int) -> int:
        max_val = max(nums)
        max_gcd_sum = 0
        
        # Iterate over possible gcd candidates
        for g in range(1, max_val + 1):
            current_sum = 0
            best_sum = 0
            window = []
            
            for num in nums:
                if num % g == 0:
                    window.append(num)
                    current_sum += num
                    if len(window) > k:
                        current_sum -= window.pop(0)
                    if len(window) >= k:
                        best_sum = max(best_sum, current_sum)
                else:
                    window.clear()
                    current_sum = 0
            
            max_gcd_sum = max(max_gcd_sum, best_sum * g)
        
        return max_gcd_sum

The Python implementation iterates over all candidate GCDs. For each GCD, it uses a sliding window approach to find contiguous subarrays of length at least k that are divisible by the candidate GCD. It tracks the maximum subarray sum and updates the overall maximum gcd-sum.

Go Solution

func maxGcdSum(nums []int, k int) int64 {
    maxVal := 0
    for _, num := range nums {
        if num > maxVal {
            maxVal = num
        }
    }

    var maxGcdSum int64 = 0

    for g := 1; g <= maxVal; g++ {
        currentSum := 0
        window := []int{}

        for _, num := range nums {
            if num%g == 0 {
                window = append(window, num)
                currentSum += num
                if len(window) > k {
                    currentSum -= window[0]
                    window = window[1:]
                }
                if len(window) >= k {
                    candidate := int64(currentSum) * int64(g)
                    if candidate > maxGcdSum {
                        maxGcdSum = candidate
                    }
                }
            } else {
                window = []int{}
                currentSum = 0
            }
        }
    }

    return maxGcdSum
}

The Go version mirrors the Python approach. The main difference is explicit handling of slices, where removing the first element requires slice reassignment. Type casting is necessary to avoid integer overflow for large sums.

Worked Examples

Example 1

nums = [2,1,4,4,4,2], k = 2
  • Maximum value is 4, candidate GCDs: 1,2,3,4

  • Candidate GCD = 4:

  • Transformed array: [0,0,4,4,4,0]

  • Sliding window of size >= 2: [4,4,4] sum = 12

  • GCD sum = 12 * 4 = 48

  • No other GCD yields a higher sum

  • Output = 48

Example 2

nums = [7,3,9,4], k = 1
  • Maximum value is 9

  • Candidate GCD = 9:

  • Transformed array: [0,0,9,0]

  • Sliding window of size >=1: [9] sum = 9

  • GCD sum = 9*9=81

  • Output = 81

Complexity Analysis

Measure Complexity Explanation
Time O(n * max(nums)) Iterate over all GCD candidates and perform linear scan of nums
Space O(n) Sliding window to track sum of contiguous elements

The reasoning is that we iterate over all GCD candidates up to the maximum value in nums, and for each candidate, we scan the array once. The space is proportional to the size of the sliding window.

Test Cases

# Provided examples
assert Solution().maxGcdSum([2,1,4,4,4,2], 2) == 48  # Example 1
assert Solution().maxGcdSum([7,3,9,4], 1) == 81       # Example 2

# Edge cases
assert Solution().maxGcdSum([1,1,1,1,1], 1) == 5      # All ones
assert Solution().maxGcdSum([6,12,18], 2) == 72       # All multiples of 6
assert Solution().maxGcdSum([2,3,5,7], 1) == 7       # Single max element
assert Solution().maxGcdSum([2], 1) == 4             # Single element array
Test Why
[2,1,4,4,4,2], 2