Facebook Pixel

3381. Maximum Subarray Sum With Length Divisible by K

Problem Description

You are given an array of integers nums and an integer k.

Your task is to find a contiguous subarray whose length is divisible by k that has the maximum possible sum. The subarray must be non-empty.

For example:

  • If k = 3, valid subarray lengths would be 3, 6, 9, 12, etc.
  • If k = 2, valid subarray lengths would be 2, 4, 6, 8, etc.

The goal is to return the maximum sum among all such valid subarrays.

Key constraints:

  • The subarray must be contiguous (elements must be adjacent in the original array)
  • The length of the subarray must be exactly divisible by k (length % k == 0)
  • You need to find the maximum sum possible among all valid subarrays
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To find the maximum sum of a subarray with length divisible by k, we need to think about how to efficiently check all possible subarrays that meet this constraint.

A key observation is that if we have a subarray from index i to index j, its length is j - i + 1. For this length to be divisible by k, we need (j - i + 1) % k == 0, which means j % k == (i - 1) % k.

This suggests we can use prefix sums combined with modular arithmetic. If we compute the cumulative sum up to each position, then the sum of a subarray from position i to j equals prefix_sum[j] - prefix_sum[i-1].

The critical insight is that we only need to track the minimum prefix sum for each possible remainder when divided by k. We can maintain an array f of size k where f[r] stores the minimum prefix sum seen so far at positions whose index modulo k equals r.

As we iterate through the array:

  1. We calculate the running sum s
  2. For the current position i, we check what's the minimum prefix sum at positions that would give us a valid subarray length (positions with remainder i % k)
  3. The maximum subarray sum ending at position i with valid length is s - f[i % k]
  4. We update f[i % k] with the minimum between its current value and our current sum s

By initializing f[-1] = 0 (which corresponds to f[k-1] = 0), we handle the case where we start from the beginning of the array. This allows us to consider subarrays that start at index 0.

This approach efficiently finds the maximum sum in O(n) time by avoiding the need to check every possible subarray explicitly.

Learn more about Prefix Sum patterns.

Solution Approach

Let's walk through the implementation step by step:

1. Initialize the tracking array:

f = [inf] * k

We create an array f of size k initialized with infinity. This array tracks the minimum prefix sum for each remainder class modulo k. We use infinity as the initial value so any actual sum will be smaller.

2. Set up initial values:

ans = -inf
s = f[-1] = 0
  • ans is initialized to negative infinity to track the maximum subarray sum found
  • s is our running prefix sum, starting at 0
  • f[-1] (which is f[k-1]) is set to 0, representing an empty prefix before the array starts

3. Main iteration through the array:

for i, x in enumerate(nums):
    s += x
    ans = max(ans, s - f[i % k])
    f[i % k] = min(f[i % k], s)

For each element at index i:

  • Update the prefix sum: Add the current element x to our running sum s
  • Check for maximum subarray: Calculate s - f[i % k]
    • This gives us the sum of a subarray ending at position i with length divisible by k
    • We subtract the minimum prefix sum that has the same remainder as i when divided by k
    • This ensures the subarray length between those positions is divisible by k
  • Update the answer: Keep track of the maximum sum found so far
  • Update the minimum prefix sum: Store the minimum prefix sum for this remainder class

Why this works:

  • When we're at position i with remainder r = i % k, we look for the minimum prefix sum at a previous position j where j % k == r
  • The subarray from position j+1 to position i has length i - j, which is divisible by k since both i and j have the same remainder modulo k
  • By keeping only the minimum prefix sum for each remainder class, we maximize the subarray sum when we subtract it from the current prefix sum

4. Return the result:

return ans

The algorithm runs in O(n) time with O(k) extra space, making it efficient for finding the maximum sum of a subarray with length divisible by k.

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 walk through an example with nums = [1, 2, -1, -2, 3, 4] and k = 3.

We need to find a contiguous subarray whose length is divisible by 3 (length could be 3, 6, etc.) with maximum sum.

Initial Setup:

  • f = [inf, inf, inf] (array of size k=3)
  • ans = -inf (to track maximum sum)
  • s = 0 (running prefix sum)
  • f[-1] = f[2] = 0 (initialize last position)

Iteration Process:

i = 0, x = 1:

  • s = 0 + 1 = 1
  • i % k = 0 % 3 = 0
  • Check: s - f[0] = 1 - inf = -inf
  • Update: ans = max(-inf, -inf) = -inf
  • Update: f[0] = min(inf, 1) = 1
  • State: f = [1, inf, 0], ans = -inf

i = 1, x = 2:

  • s = 1 + 2 = 3
  • i % k = 1 % 3 = 1
  • Check: s - f[1] = 3 - inf = -inf
  • Update: ans = max(-inf, -inf) = -inf
  • Update: f[1] = min(inf, 3) = 3
  • State: f = [1, 3, 0], ans = -inf

i = 2, x = -1:

  • s = 3 + (-1) = 2
  • i % k = 2 % 3 = 2
  • Check: s - f[2] = 2 - 0 = 2 (This represents subarray [1, 2, -1] with length 3)
  • Update: ans = max(-inf, 2) = 2
  • Update: f[2] = min(0, 2) = 0
  • State: f = [1, 3, 0], ans = 2

i = 3, x = -2:

  • s = 2 + (-2) = 0
  • i % k = 3 % 3 = 0
  • Check: s - f[0] = 0 - 1 = -1 (This represents subarray [2, -1, -2] with length 3)
  • Update: ans = max(2, -1) = 2
  • Update: f[0] = min(1, 0) = 0
  • State: f = [0, 3, 0], ans = 2

i = 4, x = 3:

  • s = 0 + 3 = 3
  • i % k = 4 % 3 = 1
  • Check: s - f[1] = 3 - 3 = 0 (This represents subarray [-1, -2, 3] with length 3)
  • Update: ans = max(2, 0) = 2
  • Update: f[1] = min(3, 3) = 3
  • State: f = [0, 3, 0], ans = 2

i = 5, x = 4:

  • s = 3 + 4 = 7
  • i % k = 5 % 3 = 2
  • Check: s - f[2] = 7 - 0 = 7 (This represents entire array [1, 2, -1, -2, 3, 4] with length 6)
  • Update: ans = max(2, 7) = 7
  • Update: f[2] = min(0, 7) = 0
  • State: f = [0, 3, 0], ans = 7

Result: The maximum sum is 7, from the entire array with length 6 (divisible by 3).

The algorithm correctly identified two valid subarrays:

  • [1, 2, -1] with sum = 2 and length = 3
  • [1, 2, -1, -2, 3, 4] with sum = 7 and length = 6

Both have lengths divisible by k=3, and the maximum sum is 7.

Solution Implementation

1class Solution:
2    def maxSubarraySum(self, nums: List[int], k: int) -> int:
3        # Initialize array to track minimum prefix sums for each remainder class modulo k
4        # min_prefix_sum[i] stores the minimum prefix sum seen so far where index % k == i
5        min_prefix_sum = [float('inf')] * k
6      
7        # Initialize the result to negative infinity (no valid subarray found yet)
8        max_sum = float('-inf')
9      
10        # Initialize prefix sum and set min_prefix_sum[-1] (equivalent to k-1) to 0
11        # This handles subarrays starting from index 0
12        prefix_sum = 0
13        min_prefix_sum[-1] = 0
14      
15        # Iterate through each element in the array
16        for index, value in enumerate(nums):
17            # Update the running prefix sum
18            prefix_sum += value
19          
20            # Calculate remainder when current index is divided by k
21            remainder = index % k
22          
23            # Update maximum sum by subtracting minimum prefix sum with same remainder
24            # This ensures the subarray length is divisible by k
25            max_sum = max(max_sum, prefix_sum - min_prefix_sum[remainder])
26          
27            # Update minimum prefix sum for current remainder class
28            min_prefix_sum[remainder] = min(min_prefix_sum[remainder], prefix_sum)
29      
30        return max_sum
31
1class Solution {
2    public long maxSubarraySum(int[] nums, int k) {
3        // Initialize array to store minimum prefix sums at each position modulo k
4        // minPrefixSum[i] will store the minimum prefix sum seen at positions j where j % k == i
5        long[] minPrefixSum = new long[k];
6      
7        // Initialize with a large value (acts as positive infinity)
8        final long INFINITY = 1L << 62;
9        Arrays.fill(minPrefixSum, INFINITY);
10      
11        // Special initialization: position (k-1) starts at 0
12        // This allows subarrays starting from index 0 to be considered
13        minPrefixSum[k - 1] = 0;
14      
15        // Track the running prefix sum
16        long prefixSum = 0;
17      
18        // Track the maximum subarray sum found
19        long maxSum = -INFINITY;
20      
21        // Iterate through each element in the array
22        for (int i = 0; i < nums.length; ++i) {
23            // Add current element to prefix sum
24            prefixSum += nums[i];
25          
26            // Calculate maximum subarray sum ending at position i
27            // We subtract the minimum prefix sum at position (i % k) to get the maximum subarray
28            // This ensures the subarray length constraint related to k is satisfied
29            maxSum = Math.max(maxSum, prefixSum - minPrefixSum[i % k]);
30          
31            // Update the minimum prefix sum for this position modulo k
32            minPrefixSum[i % k] = Math.min(minPrefixSum[i % k], prefixSum);
33        }
34      
35        return maxSum;
36    }
37}
38
1class Solution {
2public:
3    long long maxSubarraySum(vector<int>& nums, int k) {
4        using ll = long long;
5      
6        // Initialize constants
7        const ll INF = 1e18;
8      
9        // minPrefixSum[i] stores the minimum prefix sum seen so far 
10        // for indices with remainder i when divided by k
11        vector<ll> minPrefixSum(k, INF);
12      
13        // Initialize the result with negative infinity
14        ll maxSum = -INF;
15      
16        // Running prefix sum
17        ll prefixSum = 0;
18      
19        // Set minPrefixSum[k-1] = 0 to handle subarrays starting from index 0
20        // This allows selecting subarrays of length k, 2k, 3k, etc. from the beginning
21        minPrefixSum[k - 1] = 0;
22      
23        // Iterate through the array
24        for (int i = 0; i < nums.size(); ++i) {
25            // Update the prefix sum
26            prefixSum += nums[i];
27          
28            // Calculate the maximum subarray sum ending at index i
29            // with length that is a multiple of k
30            // This is done by subtracting the minimum prefix sum at compatible positions
31            maxSum = max(maxSum, prefixSum - minPrefixSum[i % k]);
32          
33            // Update the minimum prefix sum for the current remainder class
34            minPrefixSum[i % k] = min(minPrefixSum[i % k], prefixSum);
35        }
36      
37        return maxSum;
38    }
39};
40
1/**
2 * Finds the maximum sum of a subarray with length that is a multiple of k
3 * @param nums - The input array of numbers
4 * @param k - The divisor for subarray length constraint
5 * @returns The maximum sum of a valid subarray
6 */
7function maxSubarraySum(nums: number[], k: number): number {
8    // Initialize array to track minimum prefix sums at each position modulo k
9    // minPrefixSum[i] stores the minimum prefix sum seen at positions j where j % k == i
10    const minPrefixSum: number[] = Array(k).fill(Infinity);
11  
12    // Base case: empty subarray has sum 0 at position -1 (which is k-1 mod k)
13    minPrefixSum[k - 1] = 0;
14  
15    // Track the maximum subarray sum found so far
16    let maxSum: number = -Infinity;
17  
18    // Running prefix sum of the array
19    let prefixSum: number = 0;
20  
21    // Iterate through each element in the array
22    for (let i = 0; i < nums.length; ++i) {
23        // Update the running prefix sum
24        prefixSum += nums[i];
25      
26        // Calculate max subarray sum ending at position i
27        // by subtracting the minimum prefix sum at compatible positions
28        // (positions j where (i - j) is divisible by k)
29        maxSum = Math.max(maxSum, prefixSum - minPrefixSum[i % k]);
30      
31        // Update the minimum prefix sum for this position modulo k
32        minPrefixSum[i % k] = Math.min(minPrefixSum[i % k], prefixSum);
33    }
34  
35    return maxSum;
36}
37

Time and Space Complexity

Time Complexity: O(n) where n is the length of the input array nums.

The algorithm iterates through the array exactly once with a single for loop. Within each iteration, all operations are constant time:

  • Array access and update operations: f[i % k] takes O(1)
  • Addition operation: s += x takes O(1)
  • Max and min comparisons: max(ans, s - f[i % k]) and min(f[i % k], s) take O(1)

Since we perform n iterations with O(1) work per iteration, the total time complexity is O(n).

Space Complexity: O(k) where k is the input parameter.

The algorithm uses:

  • Array f of size k to store minimum prefix sums for each remainder class modulo k: O(k)
  • Variables ans and s for tracking the maximum sum and current prefix sum: O(1)
  • Loop variables i and x: O(1)

The dominant space usage is the array f, making the overall space complexity O(k).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Error with Index Mapping

Pitfall: The most common mistake is misunderstanding how the remainder classes work with array indices. When we're at index i, we need to look for previous positions with the same remainder class to ensure the subarray length is divisible by k. However, the tricky part is handling the case where we want a subarray starting from index 0.

Example of the problem:

# Incorrect approach - doesn't handle subarrays from index 0
min_prefix_sum = [float('inf')] * k
for i, x in enumerate(nums):
    prefix_sum += x
    if min_prefix_sum[i % k] != float('inf'):  # Wrong check!
        max_sum = max(max_sum, prefix_sum - min_prefix_sum[i % k])
    min_prefix_sum[i % k] = min(min_prefix_sum[i % k], prefix_sum)

Why it fails: For a subarray starting at index 0 and ending at index i where (i+1) % k == 0, we need to subtract 0 from the prefix sum. But if we don't initialize properly, we'll never consider these valid subarrays.

Solution: Initialize min_prefix_sum[k-1] = 0 before the loop. This represents a "virtual" position at index -1, which has remainder k-1 when considering modulo k arithmetic. This allows us to correctly handle subarrays starting from index 0.

2. Incorrect Order of Operations

Pitfall: Updating the minimum prefix sum before calculating the maximum subarray sum for the current position.

Incorrect code:

for i, x in enumerate(nums):
    prefix_sum += x
    min_prefix_sum[i % k] = min(min_prefix_sum[i % k], prefix_sum)  # Updated too early!
    max_sum = max(max_sum, prefix_sum - min_prefix_sum[i % k])  # Now always gets 0 or positive

Why it fails: If we update min_prefix_sum[i % k] before calculating the maximum sum, we're potentially using the current position's prefix sum to calculate a subarray of length 0 (when prefix_sum - prefix_sum = 0), which violates the non-empty subarray requirement.

Solution: Always calculate the potential maximum sum first, then update the minimum prefix sum:

max_sum = max(max_sum, prefix_sum - min_prefix_sum[i % k])  # Calculate first
min_prefix_sum[i % k] = min(min_prefix_sum[i % k], prefix_sum)  # Update after

3. Forgetting Edge Cases with Small Arrays

Pitfall: Not handling cases where the array length is smaller than k.

Example scenario:

  • nums = [1, 2], k = 3
  • The only valid subarray length would be 3, but the array only has 2 elements
  • The algorithm should return negative infinity or handle this gracefully

Solution: The current implementation handles this correctly by initializing max_sum = float('-inf'). If no valid subarray is found, it returns negative infinity. However, depending on problem requirements, you might need to explicitly check and handle this case:

if len(nums) < k:
    return float('-inf')  # or handle as per problem requirements
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

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

Load More