3026. Maximum Good Subarray Sum
Problem Description
You are given an array nums
of length n
and a positive integer k
.
A subarray is considered good if the absolute difference between its first element and last element equals exactly k
. In other words, for a subarray nums[i..j]
, it is good when |nums[i] - nums[j]| == k
.
Your task is to find the maximum sum among all good subarrays in the array. If no good subarrays exist, return 0
.
For example, if nums = [1, 2, 3, 4, 5]
and k = 3
, a subarray [2, 3, 4, 5]
would be good because |2 - 5| = 3
, which equals k
. The sum of this subarray would be 2 + 3 + 4 + 5 = 14
.
The challenge involves:
- Finding all subarrays where the absolute difference between the first and last elements equals
k
- Calculating the sum of each such subarray
- Returning the maximum sum found, or
0
if no valid subarrays exist
Intuition
To find the maximum sum of a good subarray, we need to efficiently identify all subarrays where |nums[i] - nums[j]| == k
.
Let's think about this problem differently. For any element nums[j]
at position j
, we want to find if there exists a previous element nums[i]
(where i < j
) such that:
- Either
nums[i] = nums[j] - k
- Or
nums[i] = nums[j] + k
If we find such an element, then the subarray from position i
to j
is a good subarray.
Now, to calculate the sum of subarray nums[i..j]
, we can use prefix sums. If we know the prefix sum up to position j
(let's call it sum[0..j]
) and the prefix sum up to position i-1
(let's call it sum[0..i-1]
), then the subarray sum is simply sum[0..j] - sum[0..i-1]
.
Here's the key insight: for each position j
, we want to find the position i
where nums[i]
equals either nums[j] - k
or nums[j] + k
, and the prefix sum sum[0..i-1]
is as small as possible (to maximize the subarray sum).
This leads us to use a hash table that maps each value to the minimum prefix sum seen before that value appears. As we iterate through the array:
- We check if
nums[j] - k
ornums[j] + k
exists in our hash table - If yes, we calculate the subarray sum and update our maximum
- We update the hash table with the current element and its corresponding prefix sum (only if it gives a smaller prefix sum for that value)
The reason we store the minimum prefix sum for each value is that when we later find a matching element to form a good subarray, subtracting a smaller prefix sum will give us a larger subarray sum, which is what we want to maximize.
Learn more about Prefix Sum patterns.
Solution Approach
We implement the solution using prefix sums and a hash table to efficiently find and calculate the maximum sum of good subarrays.
Data Structures:
- Hash table
p
: Maps each element value to the minimum prefix sum seen before that element appears - Variable
s
: Tracks the current prefix sum as we iterate through the array - Variable
ans
: Stores the maximum sum found, initialized to negative infinity
Algorithm Steps:
-
Initialize the hash table: Set
p[nums[0]] = 0
, which represents that beforenums[0]
, the prefix sum is 0. This allows us to consider subarrays that start from index 0. -
Iterate through the array: For each element
nums[i]
with its valuex
:- Update the prefix sum:
s += x
(nows
represents the sum fromnums[0]
tonums[i]
)
- Update the prefix sum:
-
Check for good subarrays ending at position
i
:- If
x - k
exists inp
: We found an element earlier that can form a good subarray with the current element. The subarray sum iss - p[x - k]
. Updateans = max(ans, s - p[x - k])
. - If
x + k
exists inp
: Similarly, this forms another good subarray. Updateans = max(ans, s - p[x + k])
.
- If
-
Update the hash table for future iterations:
- We prepare for the next element
nums[i + 1]
(if it exists) - Only update
p[nums[i + 1]]
if:nums[i + 1]
is not inp
(first occurrence)- OR
p[nums[i + 1]] > s
(found a smaller prefix sum for the same value)
- This ensures we always keep the minimum prefix sum for each value
- We prepare for the next element
-
Return the result:
- If
ans
is still negative infinity, no good subarray was found, return0
- Otherwise, return
ans
- If
Why this works:
- When we find
x - k
orx + k
in the hash table, it means there's a previous position where that value appeared - The prefix sum stored in
p
for that value represents the sum before that position - By subtracting this from the current prefix sum
s
, we get the exact sum of the subarray between those two positions - By always storing the minimum prefix sum for each value, we maximize the subarray sum when we calculate
s - p[value]
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 the solution with nums = [1, 5, 3, 4, 2]
and k = 3
.
We need to find subarrays where |first - last| = 3
and return the maximum sum.
Initial Setup:
p = {1: 0}
(before nums[0]=1, prefix sum is 0)s = 0
(current prefix sum)ans = -∞
(maximum sum found)
Iteration 1: i=0, x=nums[0]=1
- Update prefix sum:
s = 0 + 1 = 1
- Check for good subarrays:
x - k = 1 - 3 = -2
: Not inp
x + k = 1 + 3 = 4
: Not inp
- Update hash table for next element:
- Next element is nums[1]=5, not in
p
- Set
p[5] = 1
(prefix sum before position 1)
- Next element is nums[1]=5, not in
- Current state:
p = {1: 0, 5: 1}
,ans = -∞
Iteration 2: i=1, x=nums[1]=5
- Update prefix sum:
s = 1 + 5 = 6
- Check for good subarrays:
x - k = 5 - 3 = 2
: Not inp
x + k = 5 + 3 = 8
: Not inp
- Update hash table for next element:
- Next element is nums[2]=3, not in
p
- Set
p[3] = 6
(prefix sum before position 2)
- Next element is nums[2]=3, not in
- Current state:
p = {1: 0, 5: 1, 3: 6}
,ans = -∞
Iteration 3: i=2, x=nums[2]=3
- Update prefix sum:
s = 6 + 3 = 9
- Check for good subarrays:
x - k = 3 - 3 = 0
: Not inp
x + k = 3 + 3 = 6
: Not inp
- Update hash table for next element:
- Next element is nums[3]=4, not in
p
- Set
p[4] = 9
(prefix sum before position 3)
- Next element is nums[3]=4, not in
- Current state:
p = {1: 0, 5: 1, 3: 6, 4: 9}
,ans = -∞
Iteration 4: i=3, x=nums[3]=4
- Update prefix sum:
s = 9 + 4 = 13
- Check for good subarrays:
x - k = 4 - 3 = 1
: Found inp
!p[1] = 0
- Subarray sum =
s - p[1] = 13 - 0 = 13
- This represents subarray [1,5,3,4] where |1-4| = 3 ✓
- Update
ans = max(-∞, 13) = 13
- Subarray sum =
x + k = 4 + 3 = 7
: Not inp
- Update hash table for next element:
- Next element is nums[4]=2, not in
p
- Set
p[2] = 13
(prefix sum before position 4)
- Next element is nums[4]=2, not in
- Current state:
p = {1: 0, 5: 1, 3: 6, 4: 9, 2: 13}
,ans = 13
Iteration 5: i=4, x=nums[4]=2
- Update prefix sum:
s = 13 + 2 = 15
- Check for good subarrays:
x - k = 2 - 3 = -1
: Not inp
x + k = 2 + 3 = 5
: Found inp
!p[5] = 1
- Subarray sum =
s - p[5] = 15 - 1 = 14
- This represents subarray [5,3,4,2] where |5-2| = 3 ✓
- Update
ans = max(13, 14) = 14
- Subarray sum =
- No next element to update
- Final result:
ans = 14
The maximum sum good subarray is [5,3,4,2] with sum 14.
Solution Implementation
1class Solution:
2 def maximumSubarraySum(self, nums: List[int], k: int) -> int:
3 # Initialize maximum sum as negative infinity (no valid subarray found yet)
4 max_sum = float('-inf')
5
6 # Dictionary to store the minimum prefix sum ending just before each value
7 # Key: number value, Value: minimum prefix sum before encountering this value
8 min_prefix_before = {nums[0]: 0}
9
10 # Running prefix sum and array length
11 prefix_sum = 0
12 n = len(nums)
13
14 # Iterate through each element in the array
15 for i, current_num in enumerate(nums):
16 # Add current number to prefix sum
17 prefix_sum += current_num
18
19 # Check if there exists a number that is k less than current number
20 # If yes, we can form a subarray where difference = k
21 if current_num - k in min_prefix_before:
22 max_sum = max(max_sum, prefix_sum - min_prefix_before[current_num - k])
23
24 # Check if there exists a number that is k more than current number
25 # If yes, we can form a subarray where difference = k
26 if current_num + k in min_prefix_before:
27 max_sum = max(max_sum, prefix_sum - min_prefix_before[current_num + k])
28
29 # Update minimum prefix sum for the next number (if exists)
30 # Only update if we haven't seen this number before or found a smaller prefix sum
31 if i + 1 < n:
32 next_num = nums[i + 1]
33 if next_num not in min_prefix_before or min_prefix_before[next_num] > prefix_sum:
34 min_prefix_before[next_num] = prefix_sum
35
36 # Return 0 if no valid subarray found, otherwise return maximum sum
37 return 0 if max_sum == float('-inf') else max_sum
38
1class Solution {
2 public long maximumSubarraySum(int[] nums, int k) {
3 // Map to store the minimum prefix sum for each potential starting value
4 // Key: potential starting value, Value: minimum prefix sum before this value
5 Map<Integer, Long> minPrefixSumMap = new HashMap<>();
6
7 // Initialize with the first element as a potential starting point
8 // The prefix sum before index 0 is 0
9 minPrefixSumMap.put(nums[0], 0L);
10
11 // Running prefix sum
12 long prefixSum = 0;
13 int n = nums.length;
14
15 // Track the maximum subarray sum found
16 long maxSum = Long.MIN_VALUE;
17
18 // Iterate through each element as a potential ending point
19 for (int i = 0; i < n; i++) {
20 // Add current element to prefix sum
21 prefixSum += nums[i];
22
23 // Check if there exists a starting point where start_value = nums[i] - k
24 // This means nums[i] - start_value = k (positive difference)
25 if (minPrefixSumMap.containsKey(nums[i] - k)) {
26 // Calculate subarray sum: current prefix sum - prefix sum before start
27 maxSum = Math.max(maxSum, prefixSum - minPrefixSumMap.get(nums[i] - k));
28 }
29
30 // Check if there exists a starting point where start_value = nums[i] + k
31 // This means start_value - nums[i] = k (negative difference, but absolute is k)
32 if (minPrefixSumMap.containsKey(nums[i] + k)) {
33 // Calculate subarray sum: current prefix sum - prefix sum before start
34 maxSum = Math.max(maxSum, prefixSum - minPrefixSumMap.get(nums[i] + k));
35 }
36
37 // Prepare for next iteration: update minimum prefix sum for next element as potential start
38 if (i + 1 < n) {
39 // Only update if this is the first occurrence or we found a smaller prefix sum
40 if (!minPrefixSumMap.containsKey(nums[i + 1]) ||
41 minPrefixSumMap.get(nums[i + 1]) > prefixSum) {
42 minPrefixSumMap.put(nums[i + 1], prefixSum);
43 }
44 }
45 }
46
47 // Return 0 if no valid subarray found, otherwise return the maximum sum
48 return maxSum == Long.MIN_VALUE ? 0 : maxSum;
49 }
50}
51
1class Solution {
2public:
3 long long maximumSubarraySum(vector<int>& nums, int k) {
4 // Map to store the minimum prefix sum ending before each value
5 // Key: element value, Value: minimum prefix sum before this element
6 unordered_map<int, long long> minPrefixSum;
7
8 // Initialize with the first element (prefix sum before it is 0)
9 minPrefixSum[nums[0]] = 0;
10
11 // Running prefix sum
12 long long prefixSum = 0;
13 const int n = nums.size();
14
15 // Initialize answer to minimum value (no valid subarray found yet)
16 long long maxSum = LONG_LONG_MIN;
17
18 // Iterate through each element
19 for (int i = 0; ; ++i) {
20 // Add current element to prefix sum
21 prefixSum += nums[i];
22
23 // Check if there exists an element with value (nums[i] - k)
24 // This would make nums[i] the maximum in a valid subarray
25 auto it = minPrefixSum.find(nums[i] - k);
26 if (it != minPrefixSum.end()) {
27 // Calculate subarray sum and update maximum
28 maxSum = max(maxSum, prefixSum - it->second);
29 }
30
31 // Check if there exists an element with value (nums[i] + k)
32 // This would make nums[i] the minimum in a valid subarray
33 it = minPrefixSum.find(nums[i] + k);
34 if (it != minPrefixSum.end()) {
35 // Calculate subarray sum and update maximum
36 maxSum = max(maxSum, prefixSum - it->second);
37 }
38
39 // Check if we've processed all elements
40 if (i + 1 == n) {
41 break;
42 }
43
44 // Update minimum prefix sum for the next element
45 // Only update if this is the first occurrence or if we found a smaller prefix sum
46 it = minPrefixSum.find(nums[i + 1]);
47 if (it == minPrefixSum.end() || it->second > prefixSum) {
48 minPrefixSum[nums[i + 1]] = prefixSum;
49 }
50 }
51
52 // Return 0 if no valid subarray found, otherwise return the maximum sum
53 return maxSum == LONG_LONG_MIN ? 0 : maxSum;
54 }
55};
56
1function maximumSubarraySum(nums: number[], k: number): number {
2 // Map to store the minimum prefix sum ending just before each value
3 // Key: the next element value, Value: minimum prefix sum before that element
4 const minPrefixSumBeforeValue: Map<number, number> = new Map();
5
6 // Initialize with the first element as the potential next element (index 0)
7 minPrefixSumBeforeValue.set(nums[0], 0);
8
9 // Initialize the answer to negative infinity (no valid subarray found yet)
10 let maxSubarraySum: number = -Infinity;
11
12 // Running prefix sum of the array
13 let currentPrefixSum: number = 0;
14
15 // Get array length
16 const arrayLength: number = nums.length;
17
18 // Iterate through each element in the array
19 for (let i = 0; i < arrayLength; ++i) {
20 // Add current element to the prefix sum
21 currentPrefixSum += nums[i];
22
23 // Check if we can form a valid subarray ending at current position
24 // with difference k (current element - start element = k)
25 if (minPrefixSumBeforeValue.has(nums[i] - k)) {
26 // Calculate subarray sum and update maximum if better
27 maxSubarraySum = Math.max(
28 maxSubarraySum,
29 currentPrefixSum - minPrefixSumBeforeValue.get(nums[i] - k)!
30 );
31 }
32
33 // Check if we can form a valid subarray ending at current position
34 // with difference k (start element - current element = k)
35 if (minPrefixSumBeforeValue.has(nums[i] + k)) {
36 // Calculate subarray sum and update maximum if better
37 maxSubarraySum = Math.max(
38 maxSubarraySum,
39 currentPrefixSum - minPrefixSumBeforeValue.get(nums[i] + k)!
40 );
41 }
42
43 // Prepare for next iteration: store minimum prefix sum for next element
44 // Only update if this prefix sum is smaller than existing one or doesn't exist
45 if (i + 1 < arrayLength) {
46 const nextElement = nums[i + 1];
47 if (!minPrefixSumBeforeValue.has(nextElement) ||
48 minPrefixSumBeforeValue.get(nextElement)! > currentPrefixSum) {
49 minPrefixSumBeforeValue.set(nextElement, currentPrefixSum);
50 }
51 }
52 }
53
54 // Return 0 if no valid subarray found, otherwise return the maximum sum
55 return maxSubarraySum === -Infinity ? 0 : maxSubarraySum;
56}
57
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the array exactly once using a single for loop. Within each iteration:
- Dictionary lookups (
x - k in p
andx + k in p
) takeO(1)
time on average - Dictionary insertions and updates (
p[nums[i + 1]] = s
) takeO(1)
time on average - All other operations (comparisons, arithmetic operations, max calculations) take
O(1)
time
Since we perform n
iterations with O(1)
operations per iteration, the overall time complexity is O(n)
.
Space Complexity: O(n)
The space usage comes from:
- Dictionary
p
which stores prefix sums indexed by array elements. In the worst case, when all elements are unique, the dictionary can store up ton
key-value pairs - Variable storage (
ans
,s
,n
,i
,x
) usesO(1)
space
Therefore, the space complexity is dominated by the dictionary, resulting in O(n)
space complexity, where n
is the length of the input array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Hash Table Update Timing
The Problem:
A critical mistake is updating the hash table with the current element's prefix sum BEFORE checking for good subarrays. This can lead to considering single-element subarrays when k = 0
.
Incorrect Implementation:
for i, current_num in enumerate(nums):
prefix_sum += current_num
# WRONG: Updating before checking
if current_num not in min_prefix_before:
min_prefix_before[current_num] = prefix_sum - current_num
# Now checking - but hash table already has current element!
if current_num - k in min_prefix_before:
max_sum = max(max_sum, prefix_sum - min_prefix_before[current_num - k])
When k = 0
, the condition current_num - k in min_prefix_before
becomes current_num in min_prefix_before
, which is now always true after our update. This incorrectly considers single-element "subarrays" as valid.
The Solution: Always update the hash table for the NEXT element, not the current one. This ensures we only consider subarrays with at least 2 elements:
# Correct: Update for next element after checking current if i + 1 < n: next_num = nums[i + 1] if next_num not in min_prefix_before or min_prefix_before[next_num] > prefix_sum: min_prefix_before[next_num] = prefix_sum
Pitfall 2: Not Maintaining Minimum Prefix Sum
The Problem: Simply storing any prefix sum for each value, rather than the minimum, leads to suboptimal results.
Incorrect Implementation:
# WRONG: Always overwriting, not keeping minimum if i + 1 < n: min_prefix_before[nums[i + 1]] = prefix_sum
Why it fails:
Consider nums = [1, 5, 1, 3]
with k = 4
. If we don't maintain the minimum:
- After processing index 0:
min_prefix_before[5] = 1
- After processing index 1:
min_prefix_before[1] = 6
(overwriting) - At index 2 (value=1), checking for
1 + 4 = 5
: we get sum =7 - 6 = 1
But if we maintained the minimum:
min_prefix_before[1]
would still be0
(from initialization)- At index 2, we'd get sum =
7 - 1 = 6
(correct maximum)
The Solution: Only update when finding a smaller prefix sum:
if next_num not in min_prefix_before or min_prefix_before[next_num] > prefix_sum: min_prefix_before[next_num] = prefix_sum
Pitfall 3: Forgetting Edge Cases
The Problem: Not properly handling when no good subarrays exist, leading to returning incorrect values like negative infinity or causing errors.
The Solution:
Always check if max_sum
was updated from its initial value before returning:
return 0 if max_sum == float('-inf') else max_sum
What are the most two important steps in writing a depth first search function? (Select 2)
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!