1911. Maximum Alternating Subsequence Sum
Problem Description
You are given a 0-indexed array nums
. The alternating sum of an array is calculated by taking the sum of elements at even indices and subtracting the sum of elements at odd indices.
For example, for the array [4,2,5,3]
, the alternating sum is (4 + 5) - (2 + 3) = 4
, where elements at indices 0 and 2 are added, and elements at indices 1 and 3 are subtracted.
Your task is to find the maximum alternating sum of any subsequence from the array nums
.
Key points to understand:
- A subsequence is formed by deleting some (possibly zero) elements from the original array without changing the relative order of remaining elements
- After selecting a subsequence, the elements are reindexed starting from 0
- You need to calculate the alternating sum of this reindexed subsequence
- Return the maximum possible alternating sum among all possible subsequences
For instance, if you have [4,2,3,7,2,1,4]
and choose the subsequence [2,7,4]
, this subsequence gets reindexed as index 0, 1, 2. The alternating sum would be 2 - 7 + 4 = -1
.
The challenge is to select the optimal subsequence that maximizes the alternating sum when the selected elements are reindexed and the alternating sum formula is applied.
Intuition
When we pick elements for our subsequence, we need to decide whether each element should be placed at an even index (added) or odd index (subtracted) in the reindexed subsequence. This leads us to think about the problem in terms of states.
At any point while traversing the array, we can be in one of two states:
- We've selected an even number of elements so far (the next element would be at an even index if selected)
- We've selected an odd number of elements so far (the next element would be at an odd index if selected)
For each element in the original array, we have three choices:
- Skip it entirely
- Include it as an element at an even index (add it)
- Include it as an element at an odd index (subtract it)
The key insight is that we want to track two values as we process each element:
f[i]
: Maximum alternating sum ending at positioni
where we've selected an odd number of elements (next element would be at an odd index)g[i]
: Maximum alternating sum ending at positioni
where we've selected an even number of elements (next element would be at an even index)
For each new element x
:
- If we want to place it at an odd index (subtract it), we must have had an even count before, so:
f[i] = max(g[i-1] - x, f[i-1])
(either subtractx
from previous even state, or skipx
) - If we want to place it at an even index (add it), we must have had an odd count before, so:
g[i] = max(f[i-1] + x, g[i-1])
(either addx
from previous odd state, or skipx
)
Initially, we start with 0 elements selected (even count), so g[0] = 0
and f[0] = 0
.
The beauty of this approach is that it naturally handles all the complex decisions about which elements to include and at what positions, by simply tracking these two states and making locally optimal decisions at each step.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution uses dynamic programming with two state arrays to track the maximum alternating sum at each position.
Data Structures:
f[i]
: Array storing the maximum alternating sum up to indexi
when we've selected an odd number of elements (next element would be subtracted)g[i]
: Array storing the maximum alternating sum up to indexi
when we've selected an even number of elements (next element would be added)
Implementation Steps:
-
Initialize arrays: Create two arrays
f
andg
of sizen+1
(wheren
is the length ofnums
), initialized with zeros. The extra space allows for 1-indexed processing. -
Process each element: Iterate through the array using enumeration starting from index 1:
for i, x in enumerate(nums, 1):
-
Update states: For each element
x
at positioni
:- Update
f[i]
(odd count state):
This means either:f[i] = max(g[i - 1] - x, f[i - 1])
- Transition from even count state by subtracting
x
:g[i-1] - x
- Skip
x
and maintain the previous odd count state:f[i-1]
- Transition from even count state by subtracting
- Update
g[i]
(even count state):
This means either:g[i] = max(f[i - 1] + x, g[i - 1])
- Transition from odd count state by adding
x
:f[i-1] + x
- Skip
x
and maintain the previous even count state:g[i-1]
- Transition from odd count state by adding
- Update
-
Return the result: The maximum alternating sum is the maximum of the two final states:
return max(f[n], g[n])
Why this works:
- The DP recurrence ensures we consider all valid subsequences
- At each step, we make the optimal choice of either including the current element (at the appropriate position) or skipping it
- The two states capture whether the last selected element was at an even or odd index
- The final answer is the maximum between ending in either state
Time Complexity: O(n)
- single pass through the array
Space Complexity: O(n)
- two arrays of size n+1
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 the array nums = [4, 2, 5, 3]
.
We'll track two states:
f[i]
: max alternating sum with odd count of elements (next element would be subtracted)g[i]
: max alternating sum with even count of elements (next element would be added)
Initial State:
f[0] = 0
(no elements selected, odd count impossible)g[0] = 0
(no elements selected, even count = 0)
Processing element 4 (index 0, i=1):
f[1] = max(g[0] - 4, f[0]) = max(0 - 4, 0) = 0
- Either subtract 4 (transition from even→odd): -4
- Or skip 4 (stay in odd state): 0
- Choose: skip (0 > -4)
g[1] = max(f[0] + 4, g[0]) = max(0 + 4, 0) = 4
- Either add 4 (transition from odd→even): 4
- Or skip 4 (stay in even state): 0
- Choose: add 4 (4 > 0)
Processing element 2 (index 1, i=2):
f[2] = max(g[1] - 2, f[1]) = max(4 - 2, 0) = 2
- Either subtract 2 after adding 4: 4 - 2 = 2
- Or skip 2 (maintain previous odd state): 0
- Choose: subtract 2 (2 > 0)
g[2] = max(f[1] + 2, g[1]) = max(0 + 2, 4) = 4
- Either add 2 (from odd state): 2
- Or skip 2 (maintain previous even state): 4
- Choose: skip (4 > 2)
Processing element 5 (index 2, i=3):
f[3] = max(g[2] - 5, f[2]) = max(4 - 5, 2) = 2
- Either subtract 5 from even state: -1
- Or skip 5 (maintain odd state): 2
- Choose: skip (2 > -1)
g[3] = max(f[2] + 5, g[2]) = max(2 + 5, 4) = 7
- Either add 5 to odd state: 7
- Or skip 5 (maintain even state): 4
- Choose: add 5 (7 > 4)
Processing element 3 (index 3, i=4):
f[4] = max(g[3] - 3, f[3]) = max(7 - 3, 2) = 4
- Either subtract 3 from even state: 4
- Or skip 3 (maintain odd state): 2
- Choose: subtract 3 (4 > 2)
g[4] = max(f[3] + 3, g[3]) = max(2 + 3, 7) = 7
- Either add 3 to odd state: 5
- Or skip 3 (maintain even state): 7
- Choose: skip (7 > 5)
Final Result:
max(f[4], g[4]) = max(4, 7) = 7
The maximum alternating sum is 7, which corresponds to selecting the subsequence [4, 5]
:
- After reindexing: index 0 gets 4, index 1 gets 5
- Alternating sum: 4 - 5 = -1... Wait, that's not right!
Let me reconsider. Looking at g[4] = 7
, this represents selecting elements that sum to 7 with an even count. Tracing back:
- At
g[3] = 7
: came fromf[2] + 5 = 2 + 5
- At
f[2] = 2
: came fromg[1] - 2 = 4 - 2
- At
g[1] = 4
: came from adding 4
So the sequence is: add 4 (even index), subtract 2 (odd index), add 5 (even index) = 4 - 2 + 5 = 7
This corresponds to selecting subsequence [4, 2, 5]
which has alternating sum 4 - 2 + 5 = 7.
Solution Implementation
1class Solution:
2 def maxAlternatingSum(self, nums: List[int]) -> int:
3 """
4 Find the maximum alternating sum of a subsequence.
5 An alternating sum means: +a[0] - a[1] + a[2] - a[3] + ...
6
7 Args:
8 nums: List of positive integers
9
10 Returns:
11 Maximum alternating sum possible from any subsequence
12 """
13 n = len(nums)
14
15 # Dynamic programming arrays
16 # odd_length[i]: max alternating sum ending at index i-1 with odd length (last element is subtracted)
17 # even_length[i]: max alternating sum ending at index i-1 with even length (last element is added)
18 odd_length = [0] * (n + 1)
19 even_length = [0] * (n + 1)
20
21 # Process each element
22 for i, current_num in enumerate(nums, 1):
23 # For odd length: either extend even length by subtracting current, or keep previous odd
24 odd_length[i] = max(even_length[i - 1] - current_num, odd_length[i - 1])
25
26 # For even length: either extend odd length by adding current, or keep previous even
27 even_length[i] = max(odd_length[i - 1] + current_num, even_length[i - 1])
28
29 # Return the maximum of both possibilities at the end
30 return max(odd_length[n], even_length[n])
31
1class Solution {
2 public long maxAlternatingSum(int[] nums) {
3 int n = nums.length;
4
5 // dp[i] represents the maximum alternating sum up to index i
6 // subtractState[i]: max sum ending at index i-1 with last operation being subtraction (or no element selected)
7 // addState[i]: max sum ending at index i-1 with last operation being addition
8 long[] subtractState = new long[n + 1];
9 long[] addState = new long[n + 1];
10
11 // Process each element
12 for (int i = 1; i <= n; i++) {
13 // For subtract state at position i:
14 // Option 1: Subtract current element from previous add state
15 // Option 2: Keep previous subtract state (skip current element)
16 subtractState[i] = Math.max(addState[i - 1] - nums[i - 1], subtractState[i - 1]);
17
18 // For add state at position i:
19 // Option 1: Add current element to previous subtract state
20 // Option 2: Keep previous add state (skip current element)
21 addState[i] = Math.max(subtractState[i - 1] + nums[i - 1], addState[i - 1]);
22 }
23
24 // Return the maximum of both final states
25 return Math.max(subtractState[n], addState[n]);
26 }
27}
28
1class Solution {
2public:
3 long long maxAlternatingSum(vector<int>& nums) {
4 int n = nums.size();
5
6 // dp[i] represents the maximum alternating sum up to index i
7 // oddCount[i]: max sum when we have selected an odd number of elements
8 // evenCount[i]: max sum when we have selected an even number of elements
9 vector<long long> oddCount(n + 1, 0);
10 vector<long long> evenCount(n + 1, 0);
11
12 // Iterate through each element
13 for (int i = 1; i <= n; ++i) {
14 // For odd count: either take current element (subtract it from even count)
15 // or skip current element (keep previous odd count)
16 oddCount[i] = max(evenCount[i - 1] - nums[i - 1], oddCount[i - 1]);
17
18 // For even count: either take current element (add it to odd count)
19 // or skip current element (keep previous even count)
20 evenCount[i] = max(oddCount[i - 1] + nums[i - 1], evenCount[i - 1]);
21 }
22
23 // Return the maximum between odd and even count selections
24 return max(oddCount[n], evenCount[n]);
25 }
26};
27
1/**
2 * Calculates the maximum alternating sum of an array
3 * An alternating sum is calculated as: a[0] - a[1] + a[2] - a[3] + ...
4 * We can choose any subsequence of the array to maximize this sum
5 *
6 * @param nums - The input array of numbers
7 * @returns The maximum alternating sum possible
8 */
9function maxAlternatingSum(nums: number[]): number {
10 const arrayLength: number = nums.length;
11
12 // Dynamic programming arrays to track maximum sums
13 // oddPositionMax[i] represents max sum ending at position i with odd count of elements (positive sign)
14 const oddPositionMax: number[] = new Array(arrayLength + 1).fill(0);
15 // evenPositionMax[i] represents max sum ending at position i with even count of elements (negative sign)
16 const evenPositionMax: number[] = new Array(arrayLength + 1).fill(0);
17
18 // Iterate through each element in the array
19 for (let i = 1; i <= arrayLength; ++i) {
20 const currentElement: number = nums[i - 1];
21
22 // For odd position (adding element): either add current element to previous even position sum,
23 // or skip current element and keep previous odd position sum
24 oddPositionMax[i] = Math.max(
25 evenPositionMax[i - 1] + currentElement, // Add current element (switches from even to odd)
26 oddPositionMax[i - 1] // Skip current element
27 );
28
29 // For even position (subtracting element): either subtract current element from previous odd position sum,
30 // or skip current element and keep previous even position sum
31 evenPositionMax[i] = Math.max(
32 oddPositionMax[i - 1] - currentElement, // Subtract current element (switches from odd to even)
33 evenPositionMax[i - 1] // Skip current element
34 );
35 }
36
37 // Return the maximum between odd and even position sums at the end
38 return Math.max(oddPositionMax[arrayLength], evenPositionMax[arrayLength]);
39}
40
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the input array nums
. The algorithm iterates through the array exactly once with a single for loop, performing constant-time operations (max
comparisons and arithmetic operations) for each element.
Space Complexity: O(n)
, where n
is the length of the input array. The algorithm uses two auxiliary arrays f
and g
, each of size n + 1
, to store the dynamic programming states. Therefore, the total extra space used is 2 * (n + 1) = O(n)
.
Note: This space complexity can be optimized to O(1)
since each state f[i]
and g[i]
only depends on f[i-1]
and g[i-1]
. By using just two variables instead of arrays, we could achieve constant space complexity while maintaining the same time complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the State Definition
The Problem: Many developers incorrectly interpret the DP states as tracking whether we're at an even or odd index in the original array, rather than tracking whether we've selected an even or odd number of elements for our subsequence.
Incorrect Approach:
# WRONG: Thinking f[i] means "element at index i is at odd position"
for i in range(n):
if i % 2 == 0: # Even index in original array
f[i] = g[i-1] + nums[i]
else: # Odd index in original array
g[i] = f[i-1] - nums[i]
Why It Fails: This approach forces us to consider elements based on their original position, but subsequences get reindexed. For example, if we select elements at indices [1, 3, 5] from the original array, they become indices [0, 1, 2] in the subsequence.
Correct Understanding:
f[i]
: Maximum sum when we've selected an odd count of elements (1, 3, 5, ...) up to position ig[i]
: Maximum sum when we've selected an even count of elements (0, 2, 4, ...) up to position i
The state tracks the parity of the count of selected elements, not their original positions.
Pitfall 2: Incorrect Base Case Initialization
The Problem:
Initializing the DP arrays incorrectly, particularly setting f[0]
to a non-zero value.
Incorrect Approach:
f = [float('-inf')] * (n + 1) # WRONG: Can't have odd count with 0 elements
g = [0] * (n + 1)
Why It Fails:
Setting f[0] = -infinity
assumes it's impossible to have an odd count with zero elements selected, which is correct. However, this creates issues when we try to transition states because f[i] = max(g[i-1] - x, f[i-1])
would propagate negative infinity unnecessarily.
Correct Initialization:
f = [0] * (n + 1) # Both start at 0 g = [0] * (n + 1)
Both arrays should start at 0 because:
g[0] = 0
: Selecting 0 elements gives sum 0 (even count)f[0] = 0
: Acts as a placeholder; the first real odd-count state comes from adding the first element
Pitfall 3: Space Optimization Without Understanding Dependencies
The Problem: Attempting to optimize space by using only two variables instead of arrays, but updating them in the wrong order.
Incorrect Approach:
def maxAlternatingSum(self, nums):
odd, even = 0, 0
for x in nums:
odd = max(even - x, odd) # Uses updated 'even' if we update it first
even = max(odd + x, even) # WRONG: Uses the newly updated 'odd'
return max(odd, even)
Why It Fails:
The second update uses the newly computed odd
value instead of the previous iteration's value, breaking the DP recurrence relation.
Correct Space-Optimized Solution:
def maxAlternatingSum(self, nums):
odd, even = 0, 0
for x in nums:
# Store previous values to avoid dependency issues
new_odd = max(even - x, odd)
new_even = max(odd + x, even)
odd, even = new_odd, new_even
return max(odd, even)
Or use simultaneous assignment:
def maxAlternatingSum(self, nums):
odd, even = 0, 0
for x in nums:
odd, even = max(even - x, odd), max(odd + x, even)
return max(odd, even)
Which data structure is used in a depth first search?
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!