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
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:
- We calculate the running sum
s
- 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 remainderi % k
) - The maximum subarray sum ending at position
i
with valid length iss - f[i % k]
- We update
f[i % k]
with the minimum between its current value and our current sums
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 founds
is our running prefix sum, starting at 0f[-1]
(which isf[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 sums
- Check for maximum subarray: Calculate
s - f[i % k]
- This gives us the sum of a subarray ending at position
i
with length divisible byk
- We subtract the minimum prefix sum that has the same remainder as
i
when divided byk
- This ensures the subarray length between those positions is divisible by
k
- This gives us the sum of a subarray ending at position
- 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 remainderr = i % k
, we look for the minimum prefix sum at a previous positionj
wherej % k == r
- The subarray from position
j+1
to positioni
has lengthi - j
, which is divisible byk
since bothi
andj
have the same remainder modulok
- 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 EvaluatorExample 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]
takesO(1)
- Addition operation:
s += x
takesO(1)
- Max and min comparisons:
max(ans, s - f[i % k])
andmin(f[i % k], s)
takeO(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 sizek
to store minimum prefix sums for each remainder class modulok
:O(k)
- Variables
ans
ands
for tracking the maximum sum and current prefix sum:O(1)
- Loop variables
i
andx
: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
Which of the following is a min heap?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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!