Facebook Pixel

2036. Maximum Alternating Subarray Sum 🔒

Problem Description

You are given a 0-indexed integer array nums. Your task is to find the maximum alternating subarray sum among all possible subarrays.

A subarray is a contiguous non-empty sequence of elements from the array. For example, if nums = [1, 2, 3, 4], then [2, 3] and [1, 2, 3, 4] are subarrays, but [1, 3] is not.

The alternating subarray sum is calculated with alternating signs. For a subarray from index i to index j (inclusive), the sum is computed as:

  • nums[i] - nums[i+1] + nums[i+2] - nums[i+3] + ...

The pattern alternates between addition and subtraction, always starting with a positive sign for the first element.

For example:

  • If the subarray is [3, 5, 2], the alternating sum would be 3 - 5 + 2 = 0
  • If the subarray is [7], the alternating sum would be 7
  • If the subarray is [4, 2, 5, 3], the alternating sum would be 4 - 2 + 5 - 3 = 4

Your goal is to find the subarray that produces the largest possible alternating sum and return that maximum value.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When dealing with alternating sums, we need to think about how each element contributes to the sum depending on its position in the subarray. An element can either be added (positive contribution) or subtracted (negative contribution) based on whether it's at an odd or even position within the chosen subarray.

The key insight is that at any position in the array, we have two choices:

  1. Start a new subarray from this position (the current element becomes the first element with a positive sign)
  2. Extend an existing subarray (the current element's sign depends on the previous pattern)

This leads us to track two different states at each position:

  • f: The maximum alternating sum where the current element is added (has a positive sign)
  • g: The maximum alternating sum where the current element is subtracted (has a negative sign)

Why two states? Because when we extend a subarray:

  • If the previous element was subtracted (state g), the current element should be added
  • If the previous element was added (state f), the current element should be subtracted

For state f (adding current element):

  • We can either extend from state g (previous was subtracted, now we add): g + nums[i]
  • Or start fresh with just this element: 0 + nums[i]
  • So f = max(g, 0) + nums[i]

For state g (subtracting current element):

  • We extend from state f (previous was added, now we subtract): f - nums[i]

By maintaining these two states as we traverse the array, we can track all possible alternating subarrays ending at each position. The maximum value among all f and g values encountered gives us the answer.

Learn more about Dynamic Programming patterns.

Solution Approach

The solution uses dynamic programming with state compression to efficiently compute the maximum alternating subarray sum.

State Definition:

  • f: Maximum alternating sum of subarray ending at current position where the current element is added (positive sign)
  • g: Maximum alternating sum of subarray ending at current position where the current element is subtracted (negative sign)
  • ans: Tracks the overall maximum alternating sum found so far

Initialization: We initialize ans, f, and g to negative infinity (-inf) to handle edge cases and ensure any valid subarray sum will be larger.

State Transition: For each element x in the array, we update the states:

  1. Update f (adding current element):

    • f = max(g, 0) + x
    • This means we either:
      • Extend from the previous g state (where the previous element was subtracted), or
      • Start a new subarray from the current element (represented by 0 + x)
  2. Update g (subtracting current element):

    • g = f - x
    • We extend from the previous f state by subtracting the current element
    • Note: We use the old value of f before it was updated in step 1

Python's Simultaneous Assignment: The code uses Python's tuple unpacking feature: f, g = max(g, 0) + x, f - x

  • This ensures both f and g are updated using the values from the previous iteration
  • The right side is evaluated first using old values, then both variables are updated simultaneously

Tracking the Maximum: After updating f and g for each element, we update ans with the maximum among ans, f, and g:

  • ans = max(ans, f, g)

Time and Space Complexity:

  • Time Complexity: O(n) where n is the length of the array, as we traverse it once
  • Space Complexity: O(1) as we only use a constant amount of extra space for variables

The elegance of this solution lies in reducing what could be a complex subarray problem into tracking just two states that represent all possible alternating subarrays ending at each position.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the algorithm with nums = [3, -1, 4, 2].

Initial state:

  • ans = -inf, f = -inf, g = -inf

Processing element 3 (index 0):

  • New f = max(g, 0) + 3 = max(-inf, 0) + 3 = 0 + 3 = 3
    • This starts a new subarray [3] with alternating sum = 3
  • New g = f - 3 = -inf - 3 = -inf
    • No valid subarray where 3 is subtracted (can't start with negative)
  • Update ans = max(-inf, 3, -inf) = 3
  • State: f = 3, g = -inf, ans = 3

Processing element -1 (index 1):

  • New f = max(g, 0) + (-1) = max(-inf, 0) + (-1) = 0 + (-1) = -1
    • This starts a new subarray [-1] with alternating sum = -1
  • New g = f - (-1) = 3 - (-1) = 4
    • Extends [3] to [3, -1] with alternating sum = 3 - (-1) = 4
  • Update ans = max(3, -1, 4) = 4
  • State: f = -1, g = 4, ans = 4

Processing element 4 (index 2):

  • New f = max(g, 0) + 4 = max(4, 0) + 4 = 4 + 4 = 8
    • Extends [3, -1] to [3, -1, 4] with alternating sum = 3 - (-1) + 4 = 8
  • New g = f - 4 = -1 - 4 = -5
    • Extends [-1] to [-1, 4] with alternating sum = -1 - 4 = -5
  • Update ans = max(4, 8, -5) = 8
  • State: f = 8, g = -5, ans = 8

Processing element 2 (index 3):

  • New f = max(g, 0) + 2 = max(-5, 0) + 2 = 0 + 2 = 2
    • Starts a new subarray [2] with alternating sum = 2
  • New g = f - 2 = 8 - 2 = 6
    • Extends [3, -1, 4] to [3, -1, 4, 2] with alternating sum = 3 - (-1) + 4 - 2 = 6
  • Update ans = max(8, 2, 6) = 8
  • State: f = 2, g = 6, ans = 8

Result: The maximum alternating subarray sum is 8, achieved by the subarray [3, -1, 4] with calculation: 3 - (-1) + 4 = 8.

Solution Implementation

1class Solution:
2    def maximumAlternatingSubarraySum(self, nums: List[int]) -> int:
3        # Initialize variables
4        # max_sum: tracks the maximum alternating sum found so far
5        # odd_position_sum: maximum sum ending at current position with odd-indexed element (added)
6        # even_position_sum: maximum sum ending at current position with even-indexed element (subtracted)
7        max_sum = float('-inf')
8        odd_position_sum = float('-inf')
9        even_position_sum = float('-inf')
10      
11        # Iterate through each number in the array
12        for current_num in nums:
13            # Update the sums using dynamic programming
14            # For odd position: either extend from even position or start fresh with current number
15            # For even position: extend from previous odd position by subtracting current number
16            new_odd_sum = max(even_position_sum, 0) + current_num
17            new_even_sum = odd_position_sum - current_num
18          
19            # Update the state variables
20            odd_position_sum = new_odd_sum
21            even_position_sum = new_even_sum
22          
23            # Update the maximum sum seen so far
24            max_sum = max(max_sum, odd_position_sum, even_position_sum)
25      
26        return max_sum
27
1class Solution {
2    public long maximumAlternatingSubarraySum(int[] nums) {
3        // Use a large negative value as initial state (representing empty subarray)
4        final long NEGATIVE_INFINITY = -(1L << 60);
5      
6        // maxSum: tracks the maximum alternating sum found so far
7        long maxSum = NEGATIVE_INFINITY;
8      
9        // maxSumEndingAtOddIndex: max alternating sum ending at current position with odd length
10        // (where current element is added, i.e., has positive sign)
11        long maxSumEndingAtOddIndex = NEGATIVE_INFINITY;
12      
13        // maxSumEndingAtEvenIndex: max alternating sum ending at current position with even length
14        // (where current element is subtracted, i.e., has negative sign)
15        long maxSumEndingAtEvenIndex = NEGATIVE_INFINITY;
16      
17        // Process each element in the array
18        for (int currentNum : nums) {
19            // Calculate new max sum ending at odd index:
20            // Either extend from previous even index sum (or start fresh with 0) and add current
21            long newMaxSumEndingAtOddIndex = Math.max(maxSumEndingAtEvenIndex, 0) + currentNum;
22          
23            // Calculate new max sum ending at even index:
24            // Extend from previous odd index sum and subtract current
25            long newMaxSumEndingAtEvenIndex = maxSumEndingAtOddIndex - currentNum;
26          
27            // Update the states for next iteration
28            maxSumEndingAtOddIndex = newMaxSumEndingAtOddIndex;
29            maxSumEndingAtEvenIndex = newMaxSumEndingAtEvenIndex;
30          
31            // Update the global maximum with the best of current states
32            maxSum = Math.max(maxSum, Math.max(maxSumEndingAtOddIndex, maxSumEndingAtEvenIndex));
33        }
34      
35        return maxSum;
36    }
37}
38
1class Solution {
2public:
3    long long maximumAlternatingSubarraySum(vector<int>& nums) {
4        using ll = long long;
5      
6        // Use a large negative value as initial minimum
7        const ll INF = 1LL << 60;
8      
9        // maxSum: stores the maximum alternating sum found so far
10        ll maxSum = -INF;
11      
12        // oddLengthMax: maximum sum ending at current position with odd length (last element added)
13        ll oddLengthMax = -INF;
14      
15        // evenLengthMax: maximum sum ending at current position with even length (last element subtracted)
16        ll evenLengthMax = -INF;
17      
18        // Iterate through each element in the array
19        for (int num : nums) {
20            // Calculate new odd length sum by either:
21            // 1. Starting fresh with just current element (0 + num)
22            // 2. Extending previous even length sum by adding current element
23            ll newOddLengthMax = max(evenLengthMax, 0LL) + num;
24          
25            // Calculate new even length sum by extending previous odd length sum
26            // by subtracting current element
27            evenLengthMax = oddLengthMax - num;
28          
29            // Update odd length maximum
30            oddLengthMax = newOddLengthMax;
31          
32            // Update global maximum considering both odd and even length sums
33            maxSum = max({maxSum, oddLengthMax, evenLengthMax});
34        }
35      
36        return maxSum;
37    }
38};
39
1/**
2 * Calculates the maximum alternating subarray sum.
3 * An alternating subarray sum is calculated by alternating between adding and subtracting elements.
4 * 
5 * @param nums - The input array of numbers
6 * @returns The maximum alternating subarray sum
7 */
8function maximumAlternatingSubarraySum(nums: number[]): number {
9    // Initialize variables:
10    // maxSum: tracks the maximum alternating sum found so far
11    // evenPositionSum: maximum sum ending at current position with even-indexed element (added)
12    // oddPositionSum: maximum sum ending at current position with odd-indexed element (subtracted)
13    let maxSum: number = -Infinity;
14    let evenPositionSum: number = -Infinity;
15    let oddPositionSum: number = -Infinity;
16  
17    // Iterate through each number in the array
18    for (const currentNum of nums) {
19        // Calculate new sums using dynamic programming:
20        // For even position: either start fresh from 0 or continue from previous odd position, then add current
21        const newEvenSum: number = Math.max(oddPositionSum, 0) + currentNum;
22        // For odd position: continue from previous even position and subtract current
23        const newOddSum: number = evenPositionSum - currentNum;
24      
25        // Update the state variables
26        evenPositionSum = newEvenSum;
27        oddPositionSum = newOddSum;
28      
29        // Update the maximum sum considering both even and odd position sums
30        maxSum = Math.max(maxSum, evenPositionSum, oddPositionSum);
31    }
32  
33    return maxSum;
34}
35

Time and Space Complexity

Time Complexity: O(n), where n is the length of the array nums. The algorithm iterates through the array exactly once with a single for loop. Each iteration performs a constant number of operations (assignments, max comparisons, and basic arithmetic), so the total time complexity is linear with respect to the input size.

Space Complexity: O(1). The algorithm uses only a fixed number of variables (ans, f, g, and the loop variable x) regardless of the input size. No additional data structures are created that scale with the input, making the space complexity constant.

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

Common Pitfalls

1. Incorrect State Transition Order

One of the most common mistakes is updating the states in the wrong order, which causes the algorithm to use already-modified values instead of the previous iteration's values.

Incorrect Implementation:

# WRONG: This updates f first, then uses the NEW f value to update g
f = max(g, 0) + x
g = f - x  # This uses the NEW f, not the old one!

Why it's wrong: When we update g, we need to use the f value from the previous iteration, not the newly calculated one. The above code would incorrectly compute g = (max(g, 0) + x) - x = max(g, 0), which is not the intended transition.

Correct Solution:

# Store the old value of f before updating
old_f = f
f = max(g, 0) + x
g = old_f - x

# OR use simultaneous assignment (Python-specific)
f, g = max(g, 0) + x, f - x

2. Incorrect Initialization

Another pitfall is initializing the states to 0 instead of negative infinity.

Incorrect Implementation:

# WRONG: Starting with 0 can give incorrect results
f = 0
g = 0
ans = 0

Why it's wrong: If all numbers in the array are negative, initializing to 0 would incorrectly suggest that an empty subarray (with sum 0) is better than any actual subarray. This violates the constraint that we need a non-empty subarray.

Correct Solution:

f = float('-inf')
g = float('-inf')
ans = float('-inf')

3. Forgetting to Update Maximum After Each Iteration

Some implementations might only check the final values of f and g instead of tracking the maximum throughout the iteration.

Incorrect Implementation:

for x in nums:
    f, g = max(g, 0) + x, f - x
# WRONG: Only checking at the end
return max(f, g)

Why it's wrong: The maximum alternating sum might occur in the middle of the array, not necessarily at the end. We need to track the best result seen at any point.

Correct Solution:

ans = float('-inf')
for x in nums:
    f, g = max(g, 0) + x, f - x
    ans = max(ans, f, g)  # Update after each element
return ans

4. Misunderstanding the Alternating Pattern

Some might confuse which state represents what, leading to incorrect interpretations.

Key Understanding:

  • f represents subarrays where we're currently adding (the current element has a positive sign)
  • g represents subarrays where we're currently subtracting (the current element has a negative sign)
  • The first element of any subarray always has a positive sign

This means when starting a new subarray, we can only start with f (adding the element), never with g.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

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

Load More