Facebook Pixel

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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We check if nums[j] - k or nums[j] + k exists in our hash table
  2. If yes, we calculate the subarray sum and update our maximum
  3. 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:

  1. Initialize the hash table: Set p[nums[0]] = 0, which represents that before nums[0], the prefix sum is 0. This allows us to consider subarrays that start from index 0.

  2. Iterate through the array: For each element nums[i] with its value x:

    • Update the prefix sum: s += x (now s represents the sum from nums[0] to nums[i])
  3. Check for good subarrays ending at position i:

    • If x - k exists in p: We found an element earlier that can form a good subarray with the current element. The subarray sum is s - p[x - k]. Update ans = max(ans, s - p[x - k]).
    • If x + k exists in p: Similarly, this forms another good subarray. Update ans = max(ans, s - p[x + k]).
  4. 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 in p (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
  5. Return the result:

    • If ans is still negative infinity, no good subarray was found, return 0
    • Otherwise, return ans

Why this works:

  • When we find x - k or x + 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 Evaluator

Example 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 in p
    • x + k = 1 + 3 = 4: Not in p
  • Update hash table for next element:
    • Next element is nums[1]=5, not in p
    • Set p[5] = 1 (prefix sum before position 1)
  • 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 in p
    • x + k = 5 + 3 = 8: Not in p
  • Update hash table for next element:
    • Next element is nums[2]=3, not in p
    • Set p[3] = 6 (prefix sum before position 2)
  • 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 in p
    • x + k = 3 + 3 = 6: Not in p
  • Update hash table for next element:
    • Next element is nums[3]=4, not in p
    • Set p[4] = 9 (prefix sum before position 3)
  • 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 in p! 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
    • x + k = 4 + 3 = 7: Not in p
  • Update hash table for next element:
    • Next element is nums[4]=2, not in p
    • Set p[2] = 13 (prefix sum before position 4)
  • 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 in p
    • x + k = 2 + 3 = 5: Found in p! 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
  • 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 and x + k in p) take O(1) time on average
  • Dictionary insertions and updates (p[nums[i + 1]] = s) take O(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 to n key-value pairs
  • Variable storage (ans, s, n, i, x) uses O(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 be 0 (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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More