1749. Maximum Absolute Sum of Any Subarray
Problem Description
You are given an integer array nums
. Your task is to find the maximum absolute sum among all possible subarrays (including empty subarrays).
The absolute sum of a subarray is calculated by taking the sum of all elements in that subarray and then applying the absolute value function to the result. For example, if a subarray has elements [a, b, c]
, its absolute sum would be |a + b + c|
.
The absolute value function abs(x)
works as follows:
- If
x
is negative, thenabs(x) = -x
(makes it positive) - If
x
is non-negative, thenabs(x) = x
(stays the same)
A subarray is a contiguous sequence of elements from the array. It can be empty (in which case the sum would be 0), contain just one element, or contain multiple consecutive elements.
For example, if nums = [1, -3, 2, 3, -4]
:
- The subarray
[2, 3]
has sum5
, so its absolute sum is|5| = 5
- The subarray
[-3, 2, 3, -4]
has sum-2
, so its absolute sum is|-2| = 2
- The subarray
[2, 3, -4]
has sum1
, so its absolute sum is|1| = 1
Your goal is to return the maximum possible absolute sum among all subarrays of the given array.
Intuition
To find the maximum absolute sum, we need to think about what values could give us the largest result after applying the absolute value function. The key insight is that the maximum absolute sum could come from either:
- A subarray with the largest positive sum
- A subarray with the most negative sum (which becomes positive after applying absolute value)
This means we need to track both extremes - the maximum sum and the minimum sum possible for subarrays ending at each position.
Think of it this way: if we're looking for max(|sum|)
, this is equivalent to finding max(maximum_sum, |minimum_sum|)
. Since the absolute value of the most negative number could be larger than the most positive number, we can't just look for the maximum sum alone.
For example, if we have an array like [1, -3, -4]
:
- The maximum sum subarray might be
[1]
with sum1
- But the minimum sum subarray is
[-3, -4]
with sum-7
- After applying absolute value,
|-7| = 7
is larger than1
This leads us to use a dynamic programming approach where we maintain two running values:
f
: tracks the maximum sum of subarrays ending at the current positiong
: tracks the minimum sum of subarrays ending at the current position
At each position, we decide whether to extend the previous subarray or start fresh:
- For maximum:
f = max(f + current_element, current_element)
which simplifies tomax(f, 0) + current_element
- For minimum:
g = min(g + current_element, current_element)
which simplifies tomin(g, 0) + current_element
The answer is the maximum among all the maximum sums and the absolute values of all the minimum sums encountered during the traversal.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution implements a dynamic programming approach using Kadane's algorithm variant that tracks both maximum and minimum subarray sums simultaneously.
Implementation Details:
We use two variables to maintain our dynamic programming states:
f
: represents the maximum sum of subarrays ending at the current positiong
: represents the minimum sum of subarrays ending at the current positionans
: keeps track of the maximum absolute sum found so far
Algorithm Steps:
- Initialize
f = 0
andg = 0
as the maximum and minimum sums before processing any elements - Initialize
ans = 0
to store the result - For each element
x
in the array:- Update maximum sum:
f = max(f, 0) + x
- This means: either extend the previous maximum subarray (
f + x
) or start a new subarray from current element (0 + x
)
- This means: either extend the previous maximum subarray (
- Update minimum sum:
g = min(g, 0) + x
- This means: either extend the previous minimum subarray (
g + x
) or start a new subarray from current element (0 + x
)
- This means: either extend the previous minimum subarray (
- Update answer:
ans = max(ans, f, abs(g))
- Compare current answer with the new maximum sum and the absolute value of the new minimum sum
- Update maximum sum:
State Transition Equations:
The core logic follows these recurrence relations:
f[i] = max(f[i-1], 0) + nums[i] g[i] = min(g[i-1], 0) + nums[i]
Where:
f[i]
represents the maximum sum ending at indexi
g[i]
represents the minimum sum ending at indexi
Space Optimization:
Instead of using arrays to store all f[i]
and g[i]
values, we only need the previous state to compute the current state. This optimization reduces space complexity from O(n)
to O(1)
.
Time and Space Complexity:
- Time Complexity:
O(n)
- single pass through the array - Space Complexity:
O(1)
- only using constant extra space for variables
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 walk through the solution with nums = [2, -5, 1, -4, 3, -2]
We'll track three variables:
f
: maximum sum ending at current positiong
: minimum sum ending at current positionans
: maximum absolute sum found so far
Initial state: f = 0
, g = 0
, ans = 0
Step 1: Process element 2
f = max(0, 0) + 2 = 2
(start new subarray [2])g = min(0, 0) + 2 = 2
(start new subarray [2])ans = max(0, 2, |2|) = 2
Step 2: Process element -5
f = max(2, 0) + (-5) = -3
(extend to [2, -5])g = min(2, 0) + (-5) = -5
(start new subarray [-5])ans = max(2, -3, |-5|) = 5
Step 3: Process element 1
f = max(-3, 0) + 1 = 1
(start new subarray [1])g = min(-5, 0) + 1 = -4
(extend to [-5, 1])ans = max(5, 1, |-4|) = 5
Step 4: Process element -4
f = max(1, 0) + (-4) = -3
(extend to [1, -4])g = min(-4, 0) + (-4) = -8
(extend to [-5, 1, -4])ans = max(5, -3, |-8|) = 8
Step 5: Process element 3
f = max(-3, 0) + 3 = 3
(start new subarray [3])g = min(-8, 0) + 3 = -5
(extend to [-5, 1, -4, 3])ans = max(8, 3, |-5|) = 8
Step 6: Process element -2
f = max(3, 0) + (-2) = 1
(extend to [3, -2])g = min(-5, 0) + (-2) = -7
(extend to [-5, 1, -4, 3, -2])ans = max(8, 1, |-7|) = 8
Final answer: 8 (from the subarray [-5, 1, -4] with sum -8, giving absolute sum 8)
Solution Implementation
1class Solution:
2 def maxAbsoluteSum(self, nums: List[int]) -> int:
3 # Track maximum sum subarray (Kadane's algorithm variant)
4 max_sum = 0
5 # Track minimum sum subarray (Kadane's algorithm variant)
6 min_sum = 0
7 # Track the maximum absolute sum found so far
8 max_absolute = 0
9
10 for num in nums:
11 # Update max_sum: either extend current positive sum or start fresh from current number
12 max_sum = max(max_sum, 0) + num
13 # Update min_sum: either extend current negative sum or start fresh from current number
14 min_sum = min(min_sum, 0) + num
15 # Update answer with the maximum of current answer, max_sum, or absolute value of min_sum
16 max_absolute = max(max_absolute, max_sum, abs(min_sum))
17
18 return max_absolute
19
1class Solution {
2 public int maxAbsoluteSum(int[] nums) {
3 // Track maximum positive sum ending at current position (Kadane's algorithm for max)
4 int maxEndingHere = 0;
5
6 // Track minimum negative sum ending at current position (Kadane's algorithm for min)
7 int minEndingHere = 0;
8
9 // Store the maximum absolute sum found so far
10 int maxAbsoluteSum = 0;
11
12 // Iterate through each element in the array
13 for (int currentNum : nums) {
14 // Update max sum: either extend previous positive sum or start fresh from current
15 maxEndingHere = Math.max(maxEndingHere, 0) + currentNum;
16
17 // Update min sum: either extend previous negative sum or start fresh from current
18 minEndingHere = Math.min(minEndingHere, 0) + currentNum;
19
20 // Update result with the larger of current max sum or absolute value of min sum
21 maxAbsoluteSum = Math.max(maxAbsoluteSum, Math.max(maxEndingHere, Math.abs(minEndingHere)));
22 }
23
24 return maxAbsoluteSum;
25 }
26}
27
1class Solution {
2public:
3 int maxAbsoluteSum(vector<int>& nums) {
4 // maxSum tracks the maximum subarray sum ending at current position
5 int maxSum = 0;
6
7 // minSum tracks the minimum subarray sum ending at current position
8 int minSum = 0;
9
10 // result stores the maximum absolute sum found so far
11 int result = 0;
12
13 // Iterate through each element in the array
14 for (int& num : nums) {
15 // Update maximum subarray sum using Kadane's algorithm
16 // Reset to 0 if previous sum was negative, then add current element
17 maxSum = max(maxSum, 0) + num;
18
19 // Update minimum subarray sum (similar to Kadane's but for minimum)
20 // Reset to 0 if previous sum was positive, then add current element
21 minSum = min(minSum, 0) + num;
22
23 // Update result with the maximum of:
24 // - current result
25 // - current maximum subarray sum
26 // - absolute value of current minimum subarray sum
27 result = max({result, maxSum, abs(minSum)});
28 }
29
30 return result;
31 }
32};
33
1/**
2 * Finds the maximum absolute sum of any subarray
3 * Uses Kadane's algorithm variant to track both maximum and minimum subarray sums
4 * @param nums - Array of integers
5 * @returns Maximum absolute sum of any subarray
6 */
7function maxAbsoluteSum(nums: number[]): number {
8 // Track maximum sum ending at current position (for positive sums)
9 let maxSumEndingHere: number = 0;
10
11 // Track minimum sum ending at current position (for negative sums)
12 let minSumEndingHere: number = 0;
13
14 // Track the maximum absolute sum found so far
15 let maxAbsSum: number = 0;
16
17 // Iterate through each number in the array
18 for (const currentNum of nums) {
19 // Update max sum: either extend previous positive sum or start fresh from current number
20 maxSumEndingHere = Math.max(maxSumEndingHere, 0) + currentNum;
21
22 // Update min sum: either extend previous negative sum or start fresh from current number
23 minSumEndingHere = Math.min(minSumEndingHere, 0) + currentNum;
24
25 // Update result with the maximum of:
26 // 1. Current maximum absolute sum
27 // 2. Current maximum positive sum
28 // 3. Absolute value of current minimum negative sum
29 maxAbsSum = Math.max(maxAbsSum, maxSumEndingHere, -minSumEndingHere);
30 }
31
32 return maxAbsSum;
33}
34
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. This is because the algorithm iterates through the array exactly once, performing constant-time operations (max
, min
, abs
, and arithmetic operations) for each element.
The space complexity is O(1)
, as the algorithm only uses a fixed amount of extra space regardless of the input size. It maintains three variables (f
, g
, and ans
) that store intermediate results, and these variables do not grow with the size of the input array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Forgetting to Consider Both Positive and Negative Sums
The Problem: A common mistake is to only track the maximum sum subarray and take its absolute value, missing cases where the minimum (most negative) sum subarray actually has a larger absolute value.
Example of Incorrect Approach:
def maxAbsoluteSum(self, nums: List[int]) -> int:
max_sum = 0
current_sum = 0
for num in nums:
current_sum = max(current_sum + num, num, 0)
max_sum = max(max_sum, abs(current_sum))
return max_sum
This would fail for nums = [2, -5, 1, -4, 3, -2]
where the minimum sum subarray [-5, 1, -4]
has sum -8
and absolute value 8
, which is larger than any positive subarray sum.
Solution: Always track both the maximum and minimum sums simultaneously, as shown in the correct implementation.
Pitfall 2: Incorrect State Transition Logic
The Problem: Misunderstanding how to update the maximum and minimum sums can lead to incorrect results. A common error is:
# INCORRECT
max_sum = max(max_sum + num, num) # Missing the "start fresh with 0" option
min_sum = min(min_sum + num, num) # Missing the "start fresh with 0" option
This forces every subarray to include at least one element, which misses the case where it's better to "reset" and start a new subarray.
Solution: Use the correct transition formulas:
max_sum = max(max_sum, 0) + num # Can choose to start fresh (0) or extend
min_sum = min(min_sum, 0) + num # Can choose to start fresh (0) or extend
Pitfall 3: Not Initializing Variables Correctly
The Problem:
Initializing max_sum
or min_sum
to the first element or to very large/small values:
# INCORRECT max_sum = nums[0] # Wrong initialization min_sum = nums[0] # Wrong initialization
This prevents the algorithm from correctly considering empty subarrays or properly resetting when needed.
Solution:
Initialize both max_sum
and min_sum
to 0
, representing empty subarrays before processing any elements.
Pitfall 4: Forgetting Edge Cases
The Problem: Not handling arrays with all positive or all negative numbers correctly, or forgetting that the empty subarray (sum = 0) is valid.
Example:
For nums = [-1, -2, -3]
, the maximum absolute sum should be 6
(from subarray [-1, -2, -3]
), not 3
.
Solution: The algorithm naturally handles these cases by tracking both maximum and minimum sums and always considering the option to start fresh (empty subarray).
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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!