Before attempting this problem, you should be comfortable with:
Recursion - Breaking down problems into smaller subproblems with base cases and recursive cases
Dynamic Programming - Recognizing overlapping subproblems and using memoization to avoid redundant computation
State Machine DP - Tracking multiple states (even/odd position) and transitioning between them optimally
1. Recursion
Intuition
An alternating subsequence sum adds elements at even positions and subtracts elements at odd positions. At each index, we have two choices: include the current element in our subsequence or skip it. If we include it, the sign depends on whether we are at an even or odd position in our chosen subsequence.
The recursive approach explores both choices at every index and tracks whether the next element we pick would be at an even or odd position.
Algorithm
Define dfs(i, even) where i is the current index and even indicates if the next picked element contributes positively.
Base case: if i == n, return 0.
If even is true, we can either:
Pick nums[i] (adding it) and recurse with even = false.
Skip and recurse with even = true.
If even is false, we can either:
Pick nums[i] (subtracting it) and recurse with even = true.
classSolution:defmaxAlternatingSum(self, nums: List[int])->int:defdfs(i, even):if i ==len(nums):return0
total = nums[i]if even else-nums[i]returnmax(total + dfs(i +1,not even), dfs(i +1, even))return dfs(0,True)
publicclassSolution{publiclongmaxAlternatingSum(int[] nums){returndfs(nums,0,true);}privatelongdfs(int[] nums,int i,boolean even){if(i == nums.length){return0;}long total = even ? nums[i]:-nums[i];returnMath.max(total +dfs(nums, i +1,!even),dfs(nums, i +1, even));}}
classSolution{public:longlongmaxAlternatingSum(vector<int>& nums){returndfs(nums,0,true);}private:longlongdfs(vector<int>& nums,int i,bool even){if(i == nums.size()){return0;}longlong total = even ? nums[i]:-nums[i];returnmax(total +dfs(nums, i +1,!even),dfs(nums, i +1, even));}};
funcmaxAlternatingSum(nums []int)int64{var dfs func(i int, even bool)int64
dfs =func(i int, even bool)int64{if i ==len(nums){return0}var total int64if even {
total =int64(nums[i])}else{
total =-int64(nums[i])}returnmax64(total+dfs(i+1,!even),dfs(i+1, even))}returndfs(0,true)}funcmax64(a, b int64)int64{if a > b {return a
}return b
}
class Solution {funmaxAlternatingSum(nums: IntArray): Long {fundfs(i: Int, even: Boolean): Long {if(i == nums.size)return0Lval total =if(even) nums[i].toLong()else-nums[i].toLong()returnmaxOf(total +dfs(i +1,!even),dfs(i +1, even))}returndfs(0,true)}}
classSolution{funcmaxAlternatingSum(_ nums:[Int])->Int{funcdfs(_ i:Int,_ even:Bool)->Int{if i == nums.count {return0}let total = even ? nums[i]:-nums[i]returnmax(total +dfs(i +1,!even),dfs(i +1, even))}returndfs(0,true)}}
Time & Space Complexity
Time complexity: O(2n)
Space complexity: O(n) for recursion stack.
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution has overlapping subproblems. The state (i, even) can be reached multiple times through different paths, so we can cache results to avoid redundant computation.
Since there are n possible indices and 2 possible parity states, we have O(n) unique states. Memoizing these transforms the exponential time complexity into linear.
Algorithm
Create a memoization table dp[i][even] initialized to -1 (unvisited).
Define dfs(i, even) with the same logic as before.
Before computing, check if dp[i][even] is cached and return it if so.
After computing the result, store it in dp[i][even].
Return dfs(0, 1) where 1 represents the even state.
classSolution:defmaxAlternatingSum(self, nums: List[int])->int:
dp ={}defdfs(i, even):if i ==len(nums):return0if(i, even)in dp:return dp[(i, even)]
total = nums[i]if even else-nums[i]
dp[(i, even)]=max(total + dfs(i +1,not even), dfs(i +1, even))return dp[(i, even)]return dfs(0,True)
publicclassSolution{privatelong dp[][];publiclongmaxAlternatingSum(int[] nums){int n = nums.length;
dp =newlong[n][2];for(int i =0; i < n; i++){
dp[i][0]=-1;
dp[i][1]=-1;}returndfs(nums,0,1);}privatelongdfs(int[] nums,int i,int even){if(i == nums.length){return0;}if(dp[i][even]!=-1){return dp[i][even];}long total = even ==1? nums[i]:-nums[i];
dp[i][even]=Math.max(total +dfs(nums, i +1,1- even),dfs(nums, i +1, even));return dp[i][even];}}
classSolution{
vector<vector<longlong>> dp;public:longlongmaxAlternatingSum(vector<int>& nums){
dp.assign(nums.size(),vector<longlong>(2,-1));returndfs(nums,0,true);}private:longlongdfs(vector<int>& nums,int i,bool even){if(i == nums.size()){return0;}if(dp[i][even]!=-1){return dp[i][even];}longlong total = even ? nums[i]:-nums[i];
dp[i][even]=max(total +dfs(nums, i +1,!even),dfs(nums, i +1, even));return dp[i][even];}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/maxAlternatingSum(nums){const n = nums.length;const dp = Array.from({length: n },()=>Array(2).fill(-1));constdfs=(i, even)=>{if(i === n){return0;}if(dp[i][even]!==-1){return dp[i][even];}const total = even ===1? nums[i]:-nums[i];
dp[i][even]= Math.max(
total +dfs(i +1,1- even),dfs(i +1, even),);return dp[i][even];};returndfs(0,1);}}
funcmaxAlternatingSum(nums []int)int64{
n :=len(nums)
dp :=make([][]int64, n)for i :=range dp {
dp[i]=[]int64{-1,-1}}var dfs func(i, even int)int64
dfs =func(i, even int)int64{if i == n {return0}if dp[i][even]!=-1{return dp[i][even]}var total int64if even ==1{
total =int64(nums[i])}else{
total =-int64(nums[i])}
dp[i][even]=max64(total+dfs(i+1,1-even),dfs(i+1, even))return dp[i][even]}returndfs(0,1)}funcmax64(a, b int64)int64{if a > b {return a
}return b
}
class Solution {funmaxAlternatingSum(nums: IntArray): Long {val n = nums.size
val dp =Array(n){LongArray(2){-1L}}fundfs(i: Int, even: Int): Long {if(i == n)return0Lif(dp[i][even]!=-1L)return dp[i][even]val total =if(even ==1) nums[i].toLong()else-nums[i].toLong()
dp[i][even]=maxOf(total +dfs(i +1,1- even),dfs(i +1, even))return dp[i][even]}returndfs(0,1)}}
classSolution{funcmaxAlternatingSum(_ nums:[Int])->Int{let n = nums.count
var dp =[[Int]](repeating:[-1,-1], count: n)funcdfs(_ i:Int,_ even:Int)->Int{if i == n {return0}if dp[i][even]!=-1{return dp[i][even]}let total = even ==1? nums[i]:-nums[i]
dp[i][even]=max(total +dfs(i +1,1- even),dfs(i +1, even))return dp[i][even]}returndfs(0,1)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Dynamic Programming (Bottom-Up)
Intuition
We can convert the top-down approach to bottom-up by filling the DP table iteratively. For each position, we track two values: the maximum alternating sum if the next element we pick would be at an even position, and the maximum if it would be at an odd position.
Working backwards from the end of the array, we compute these values based on the two choices at each position (pick or skip).
Algorithm
Create a 2D array dp[n+1][2] initialized to 0.
Iterate from i = n-1 down to 0:
dp[i][1] (even) = max of picking nums[i] plus dp[i+1][0], or skipping with dp[i+1][1].
dp[i][0] (odd) = max of picking -nums[i] plus dp[i+1][1], or skipping with dp[i+1][0].
Return dp[0][1] since we start expecting an even-positioned pick.
publicclassSolution{publiclongmaxAlternatingSum(int[] nums){int n = nums.length;long[][] dp =newlong[n +1][2];// dp[i][0] -> odd, dp[i][1] -> evenfor(int i = n -1; i >=0; i--){
dp[i][1]=Math.max(nums[i]+ dp[i +1][0], dp[i +1][1]);// even
dp[i][0]=Math.max(-nums[i]+ dp[i +1][1], dp[i +1][0]);// odd}return dp[0][1];}}
classSolution{public:longlongmaxAlternatingSum(vector<int>& nums){int n = nums.size();
vector<vector<longlong>>dp(n +1,vector<longlong>(2,0));// dp[i][0] -> odd, dp[i][1] -> evenfor(int i = n -1; i >=0; i--){
dp[i][1]=max(nums[i]+ dp[i +1][0], dp[i +1][1]);// even
dp[i][0]=max(-nums[i]+ dp[i +1][1], dp[i +1][0]);// odd}return dp[0][1];}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/maxAlternatingSum(nums){const n = nums.length;const dp = Array.from({length: n +1},()=>[0,0]);// dp[i][0] -> odd, dp[i][1] -> evenfor(let i = n -1; i >=0; i--){
dp[i][1]= Math.max(nums[i]+ dp[i +1][0], dp[i +1][1]);// even
dp[i][0]= Math.max(-nums[i]+ dp[i +1][1], dp[i +1][0]);// odd}return dp[0][1];// Result starts with even index}}
funcmaxAlternatingSum(nums []int)int64{
n :=len(nums)
dp :=make([][]int64, n+1)for i :=range dp {
dp[i]=[]int64{0,0}}for i := n -1; i >=0; i--{
dp[i][1]=max64(int64(nums[i])+dp[i+1][0], dp[i+1][1])// even
dp[i][0]=max64(-int64(nums[i])+dp[i+1][1], dp[i+1][0])// odd}return dp[0][1]}funcmax64(a, b int64)int64{if a > b {return a
}return b
}
class Solution {funmaxAlternatingSum(nums: IntArray): Long {val n = nums.size
val dp =Array(n +1){LongArray(2)}for(i in n -1 downTo 0){
dp[i][1]=maxOf(nums[i]+ dp[i +1][0], dp[i +1][1])// even
dp[i][0]=maxOf(-nums[i]+ dp[i +1][1], dp[i +1][0])// odd}return dp[0][1]}}
classSolution{funcmaxAlternatingSum(_ nums:[Int])->Int{let n = nums.count
var dp =[[Int]](repeating:[0,0], count: n +1)for i instride(from: n -1, through:0, by:-1){
dp[i][1]=max(nums[i]+ dp[i +1][0], dp[i +1][1])// even
dp[i][0]=max(-nums[i]+ dp[i +1][1], dp[i +1][0])// odd}return dp[0][1]}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
4. Dynamic Programming (Space Optimized)
Intuition
Notice that each state only depends on the state at the next index. We do not need the entire DP table; just two variables suffice to track the best even-position sum and odd-position sum for the suffix starting at the current index.
This reduces space from O(n) to O(1) while maintaining the same logic.
Algorithm
Initialize sumEven = 0 and sumOdd = 0.
Iterate from i = n-1 down to 0:
tmpEven = max(nums[i] + sumOdd, sumEven) represents the best sum if next pick is even.
tmpOdd = max(-nums[i] + sumEven, sumOdd) represents the best sum if next pick is odd.
funcmaxAlternatingSum(nums []int)int64{var sumEven, sumOdd int64=0,0for i :=len(nums)-1; i >=0; i--{
tmpEven :=max64(int64(nums[i])+sumOdd, sumEven)
tmpOdd :=max64(-int64(nums[i])+sumEven, sumOdd)
sumEven, sumOdd = tmpEven, tmpOdd
}return sumEven
}funcmax64(a, b int64)int64{if a > b {return a
}return b
}
A subsequence does not require elements to be contiguous, while a subarray does. In this problem, you can skip elements freely. For example, from [4, 2, 5, 3], you can pick [4, 2, 5] (indices 0, 1, 2) or [4, 5] (indices 0, 2). Treating this as a subarray problem leads to incorrect answers.
Misunderstanding the Alternating Pattern
The alternating sum starts with addition for the first picked element, then subtracts the second, adds the third, and so on. The pattern is based on the position within the chosen subsequence, not the original array indices. Starting with subtraction or miscounting the parity will produce wrong results.
Integer Overflow
The sum can exceed 32-bit integer limits when the array is large and contains values up to 105. Use long in Java/C++ or ensure your language handles big integers. Failing to account for this causes overflow errors on larger test cases.