1746. Maximum Subarray Sum After One Operation
Problem Description
You have an integer array nums
and need to perform exactly one operation on it. The operation allows you to select any element nums[i]
and replace it with its square value nums[i] * nums[i]
.
After performing this single operation, you need to find the maximum possible sum of any contiguous subarray. The subarray must contain at least one element (non-empty).
For example, if you have an array [2, -1, 3]
, you could:
- Square the first element to get
[4, -1, 3]
, then the maximum subarray sum would be4 + (-1) + 3 = 6
- Square the second element to get
[2, 1, 3]
, then the maximum subarray sum would be2 + 1 + 3 = 6
- Square the third element to get
[2, -1, 9]
, then the maximum subarray sum would be9
The goal is to strategically choose which element to square such that the resulting maximum subarray sum is as large as possible, then return that maximum sum.
The solution uses dynamic programming with two states:
f
: tracks the maximum subarray sum ending at the current position without using the square operationg
: tracks the maximum subarray sum ending at the current position with the square operation already used
At each position, we update both states and keep track of the overall maximum seen so far.
Intuition
The key insight is that this problem is a variation of the classic maximum subarray sum problem (Kadane's algorithm), but with a twist - we must use the squaring operation exactly once.
When thinking about any subarray, we have two scenarios to consider:
- The squaring operation hasn't been used yet in this subarray
- The squaring operation has already been used somewhere in this subarray
This naturally leads us to track two different states as we iterate through the array. At each position, we need to know:
- What's the best subarray sum ending here if we haven't used the square operation?
- What's the best subarray sum ending here if we've already used the square operation?
For any element at position i
, we have choices:
-
If we haven't squared anything yet (
f
state), we can either:- Continue the previous subarray or start fresh (classic Kadane's logic):
max(f, 0) + x
- Continue the previous subarray or start fresh (classic Kadane's logic):
-
If we want to have used the square operation by position
i
(g
state), we can either:- Square the current element and combine with the best non-squared subarray ending before it:
max(f, 0) + x * x
- Or we already squared something earlier and just add the current element:
g + x
- Square the current element and combine with the best non-squared subarray ending before it:
The beauty of this approach is that it handles all cases: squaring negative numbers (which become positive), squaring positive numbers (which become larger), and choosing the optimal position to apply the operation. By maintaining these two states and taking their maximum at each step, we ensure we find the globally optimal solution.
The initialization with f = g = 0
works because we can always choose to start a new subarray at any position, and the constraint that we must use the operation exactly once is naturally enforced by our state transitions.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution uses dynamic programming with two state variables to track the maximum subarray sum with and without using the squaring operation.
State Variables:
f
: Maximum subarray sum ending at the current position without using the square operationg
: Maximum subarray sum ending at the current position with the square operation already usedans
: Global maximum found so far (initialized to negative infinity to handle all negative arrays)
Algorithm Steps:
-
Initialize states: Start with
f = g = 0
andans = -inf
-
Iterate through each element
x
in nums:a. Update
f
(no square used):ff = max(f, 0) + x
This follows Kadane's algorithm - either extend the previous subarray or start fresh if the previous sum was negative.
b. Update
g
(square operation used):gg = max(max(f, 0) + x * x, g + x)
We take the maximum of two options:
- Square the current element:
max(f, 0) + x * x
(use the best non-squared subarray before this and square current element) - Already squared earlier:
g + x
(extend the subarray that already used the square operation)
c. Update states and answer:
f, g = ff, gg ans = max(ans, f, g)
Store the new states and update the global maximum.
- Square the current element:
-
Return the result: After processing all elements,
ans
contains the maximum possible subarray sum with exactly one square operation.
Why this works:
- The algorithm ensures we consider all possible positions to apply the square operation
- By tracking both states simultaneously, we can make optimal decisions at each position
- The
max(f, 0)
pattern ensures we start fresh when continuing would decrease our sum - The final answer considers both states at every position, capturing all possible optimal subarrays
Time Complexity: O(n)
- single pass through the array
Space Complexity: O(1)
- only using constant extra space for state variables
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 algorithm with the array nums = [2, -3, 4]
.
Initial State:
f = 0
(max subarray sum ending here, no square used)g = 0
(max subarray sum ending here, square used)ans = -inf
(global maximum)
Step 1: Process element x = 2 (index 0)
- Calculate new f:
ff = max(0, 0) + 2 = 2
- We either start fresh or extend. Since f=0, we get 0+2=2
- Calculate new g:
gg = max(max(0, 0) + 2*2, 0 + 2) = max(4, 2) = 4
- Option 1: Square current element: 0 + 4 = 4
- Option 2: Already squared earlier: 0 + 2 = 2
- Choose maximum: 4
- Update states:
f = 2, g = 4
- Update answer:
ans = max(-inf, 2, 4) = 4
Step 2: Process element x = -3 (index 1)
- Calculate new f:
ff = max(2, 0) + (-3) = 2 - 3 = -1
- Extend previous subarray: 2 + (-3) = -1
- Calculate new g:
gg = max(max(2, 0) + (-3)*(-3), 4 + (-3)) = max(11, 1) = 11
- Option 1: Square current element: 2 + 9 = 11
- Option 2: Already squared earlier: 4 + (-3) = 1
- Choose maximum: 11
- Update states:
f = -1, g = 11
- Update answer:
ans = max(4, -1, 11) = 11
Step 3: Process element x = 4 (index 2)
- Calculate new f:
ff = max(-1, 0) + 4 = 0 + 4 = 4
- Start fresh since previous f=-1 is negative
- Calculate new g:
gg = max(max(-1, 0) + 4*4, 11 + 4) = max(16, 15) = 16
- Option 1: Square current element: 0 + 16 = 16
- Option 2: Already squared earlier: 11 + 4 = 15
- Choose maximum: 16
- Update states:
f = 4, g = 16
- Update answer:
ans = max(11, 4, 16) = 16
Result: The maximum possible subarray sum is 16.
This comes from the subarray [-3, 4]
where we square the 4 to get [-3, 16]
, giving sum = -3 + 16 = 13. Wait, let me recalculate...
Actually, looking at step 3 more carefully:
- The
g = 16
represents just taking element 4 and squaring it (starting fresh), which gives us 16. - This is indeed the optimal solution: take the single element subarray
[4]
and square it to get[16]
.
The algorithm correctly identified that the best strategy is to take just the element 4 alone and square it, yielding a maximum sum of 16.
Solution Implementation
1class Solution:
2 def maxSumAfterOperation(self, nums: List[int]) -> int:
3 # Dynamic programming approach to find maximum subarray sum with at most one square operation
4 # no_square: maximum sum ending at current position without using square operation
5 # with_square: maximum sum ending at current position with square operation used
6 no_square = 0
7 with_square = 0
8 max_sum = float('-inf')
9
10 for num in nums:
11 # Calculate new maximum sum without square operation
12 # Either extend previous subarray or start new one from current element
13 new_no_square = max(no_square, 0) + num
14
15 # Calculate new maximum sum with square operation
16 # Two choices:
17 # 1. Square current element and possibly extend previous no-square subarray
18 # 2. Extend previous with-square subarray (square already used)
19 new_with_square = max(max(no_square, 0) + num * num, with_square + num)
20
21 # Update states for next iteration
22 no_square = new_no_square
23 with_square = new_with_square
24
25 # Track the maximum sum seen so far
26 max_sum = max(max_sum, no_square, with_square)
27
28 return max_sum
29
1class Solution {
2 public int maxSumAfterOperation(int[] nums) {
3 // maxSumWithoutSquare: Maximum subarray sum ending at current position without using square operation
4 int maxSumWithoutSquare = 0;
5
6 // maxSumWithSquare: Maximum subarray sum ending at current position with square operation used
7 int maxSumWithSquare = 0;
8
9 // result: Track the overall maximum sum found so far
10 int result = Integer.MIN_VALUE;
11
12 // Iterate through each element in the array
13 for (int currentNum : nums) {
14 // Calculate new maximum sum without square operation
15 // Either extend previous subarray or start new subarray from current element
16 int newMaxSumWithoutSquare = Math.max(maxSumWithoutSquare, 0) + currentNum;
17
18 // Calculate new maximum sum with square operation
19 // Two options:
20 // 1. Square current element and possibly extend previous non-squared subarray
21 // 2. Extend previous squared subarray with current element (not squared)
22 int newMaxSumWithSquare = Math.max(
23 Math.max(maxSumWithoutSquare, 0) + currentNum * currentNum, // Option 1: Square current element
24 maxSumWithSquare + currentNum // Option 2: Extend squared subarray
25 );
26
27 // Update states for next iteration
28 maxSumWithoutSquare = newMaxSumWithoutSquare;
29 maxSumWithSquare = newMaxSumWithSquare;
30
31 // Update the global maximum result
32 result = Math.max(result, Math.max(maxSumWithoutSquare, maxSumWithSquare));
33 }
34
35 return result;
36 }
37}
38
1class Solution {
2public:
3 int maxSumAfterOperation(vector<int>& nums) {
4 // dp_no_square: max sum of subarray ending at current position without squaring
5 int dp_no_square = 0;
6
7 // dp_with_square: max sum of subarray ending at current position with one element squared
8 int dp_with_square = 0;
9
10 // max_sum: tracks the maximum sum found so far
11 int max_sum = INT_MIN;
12
13 // Iterate through each element in the array
14 for (int current_num : nums) {
15 // Calculate new max sum without squaring
16 // Either extend previous subarray or start new subarray from current element
17 int new_dp_no_square = max(dp_no_square, 0) + current_num;
18
19 // Calculate new max sum with squaring
20 // Two options:
21 // 1. Square current element and possibly extend previous non-squared subarray
22 // 2. Extend previous squared subarray with current element (not squared)
23 int new_dp_with_square = max(
24 max(dp_no_square, 0) + current_num * current_num, // Option 1: square current
25 dp_with_square + current_num // Option 2: already squared before
26 );
27
28 // Update states for next iteration
29 dp_no_square = new_dp_no_square;
30 dp_with_square = new_dp_with_square;
31
32 // Update the maximum sum seen so far
33 max_sum = max({max_sum, dp_no_square, dp_with_square});
34 }
35
36 return max_sum;
37 }
38};
39
1function maxSumAfterOperation(nums: number[]): number {
2 // dpNoSquare: maximum sum of subarray ending at current position without squaring any element
3 let dpNoSquare: number = 0;
4
5 // dpWithSquare: maximum sum of subarray ending at current position with exactly one element squared
6 let dpWithSquare: number = 0;
7
8 // maxSum: tracks the overall maximum sum found so far
9 let maxSum: number = Number.MIN_SAFE_INTEGER;
10
11 // Iterate through each element in the array
12 for (const currentNum of nums) {
13 // Calculate new maximum sum without squaring
14 // Either extend the previous subarray or start a new subarray from current element
15 const newDpNoSquare: number = Math.max(dpNoSquare, 0) + currentNum;
16
17 // Calculate new maximum sum with one element squared
18 // Two choices:
19 // 1. Square the current element and possibly extend previous non-squared subarray
20 // 2. Extend the previous subarray (which already has one squared element) with current element (not squared)
21 const newDpWithSquare: number = Math.max(
22 Math.max(dpNoSquare, 0) + currentNum * currentNum, // Option 1: square current element
23 dpWithSquare + currentNum // Option 2: already squared an element before
24 );
25
26 // Update dynamic programming states for next iteration
27 dpNoSquare = newDpNoSquare;
28 dpWithSquare = newDpWithSquare;
29
30 // Update the overall maximum sum seen so far
31 maxSum = Math.max(maxSum, dpNoSquare, dpWithSquare);
32 }
33
34 return maxSum;
35}
36
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, performing a constant amount of operations for each element (calculating ff
, gg
, updating variables, and finding maximum values).
Space Complexity: O(1)
. The algorithm uses only a fixed amount of extra space regardless of the input size. The variables f
, g
, ff
, gg
, and ans
are the only additional memory used, which doesn't scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle All-Negative Arrays
A common mistake is initializing the maximum result to 0, which fails when all array elements are negative.
Incorrect:
max_sum = 0 # Wrong! Fails for arrays like [-5, -2, -3]
Correct:
max_sum = float('-inf') # Handles all-negative arrays correctly
2. Incorrectly Updating the with_square
State
Many implementations forget that once the square operation is used, we cannot use it again. A common error is allowing multiple squares:
Incorrect:
# This allows squaring multiple elements
new_with_square = max(no_square + num * num, with_square + num * num)
Correct:
# Square current element OR extend already-squared subarray (no more squares)
new_with_square = max(max(no_square, 0) + num * num, with_square + num)
3. Not Considering Starting Fresh When Previous Sum is Negative
Failing to reset when the previous subarray sum becomes negative leads to suboptimal results:
Incorrect:
new_no_square = no_square + num # Always extends, even if no_square < 0 new_with_square = no_square + num * num # Same issue
Correct:
new_no_square = max(no_square, 0) + num
new_with_square = max(max(no_square, 0) + num * num, with_square + num)
4. Updating States Before Using Them
Modifying state variables before all calculations are complete causes incorrect transitions:
Incorrect:
for num in nums:
no_square = max(no_square, 0) + num # Modified immediately
with_square = max(max(no_square, 0) + num * num, with_square + num) # Uses modified no_square!
Correct:
for num in nums:
new_no_square = max(no_square, 0) + num
new_with_square = max(max(no_square, 0) + num * num, with_square + num)
no_square = new_no_square # Update after all calculations
with_square = new_with_square
5. Only Checking Final States Instead of All Positions
The maximum subarray might end at any position, not just the last element:
Incorrect:
for num in nums:
# ... update states ...
return max(no_square, with_square) # Only checks final position
Correct:
for num in nums:
# ... update states ...
max_sum = max(max_sum, no_square, with_square) # Check at every position
return max_sum
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
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!