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 be3 - 5 + 2 = 0
- If the subarray is
[7]
, the alternating sum would be7
- If the subarray is
[4, 2, 5, 3]
, the alternating sum would be4 - 2 + 5 - 3 = 4
Your goal is to find the subarray that produces the largest possible alternating sum and return that maximum value.
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:
- Start a new subarray from this position (the current element becomes the first element with a positive sign)
- 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:
-
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
)
- Extend from the previous
-
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
andg
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)
wheren
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 EvaluatorExample 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
- This starts a new subarray
- 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
- This starts a new subarray
- New
g = f - (-1) = 3 - (-1) = 4
- Extends
[3]
to[3, -1]
with alternating sum = 3 - (-1) = 4
- Extends
- 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
- Extends
- New
g = f - 4 = -1 - 4 = -5
- Extends
[-1]
to[-1, 4]
with alternating sum = -1 - 4 = -5
- Extends
- 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
- Starts a new subarray
- New
g = f - 2 = 8 - 2 = 6
- Extends
[3, -1, 4]
to[3, -1, 4, 2]
with alternating sum = 3 - (-1) + 4 - 2 = 6
- Extends
- 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
.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!