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
0if 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] - kornums[j] + kexists 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 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
381class 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}
511class 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};
561function 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}
57Solution 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(nowsrepresents the sum fromnums[0]tonums[i])
- Update the prefix sum:
-
Check for good subarrays ending at position
i:- If
x - kexists 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 + kexists 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
ansis still negative infinity, no good subarray was found, return0 - Otherwise, return
ans
- If
Why this works:
- When we find
x - korx + kin the hash table, it means there's a previous position where that value appeared - The prefix sum stored in
pfor 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 inpx + 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 inpx + 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 inpx + 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 inpx + 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.
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 pandx + 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
pwhich stores prefix sums indexed by array elements. In the worst case, when all elements are unique, the dictionary can store up tonkey-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 Given an array of integers arr and a target value find a subarray that sums to the target Return the start and end indices of the subarray Input arr 1 20 3 30 5 4 target 7 Output 1 4 Explanation The subarray 20 3 30 from index 1 inclusive
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!