Introduction & Problem Explanation
The Maximum Subarray problem asks us to find a contiguous subarray within a one-dimensional array of numbers (which contains at least one positive number) that has the largest sum.
For example, in the array [-2, 1, -3, 4, -1, 2, 1, -5, 4], the contiguous subarray with the
largest sum is [4, -1, 2, 1], which sums up to 6. Finding this efficiently is
critical because contiguous sequence maximization is a common subproblem in data analysis and signal
processing.
Imagine riding a rollercoaster with positive highs (thrill) and negative drops (fear/boredom). You want to identify the single continuous segment of the ride that gave you the absolute highest aggregate happiness. As you traverse the track, you keep track of your running streak of happiness. If your cumulative happiness drops below zero, it means the sequence is actually dragging you down; so you discard it, reset your running streak, and start tracking a new streak from the next point. Throughout the entire ride, you note down the highest happiness score your streak ever reached. That peak score is the maximum subarray sum!
The Algorithmic Approach
1. The Naive Approach (O(n²))
We can check every possible starting and ending index for all subarrays, sum their elements, and record the
maximum. This requires two nested loops, yielding a time complexity of O(n²). For large datasets,
this approach is extremely slow and impractical.
2. Kadane's Algorithm (O(n) Time, O(1) Space)
Kadane's algorithm is a classic dynamic programming technique that solves the problem in a single pass. At
each index, we make a local greedy decision: Is it better to add the current element to our existing running
sum, or should we discard the current sum and start a brand-new subarray starting exactly at the current
element? By taking Math.max(nums[i], currSum + nums[i]), we dynamically build the optimal
subarray sum in O(n) time using only O(1) extra space.
Step-by-Step Execution Walkthrough
Let's trace Kadane's algorithm on the input array nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]:
- Step 1 (Initialization): We initialize two variables:
currSum(current running subarray sum) andmaxSum(global maximum subarray sum seen so far). We set both to the first element of the array:currSum = -2maxSum = -2
- Step 2 (Index i = 1, Value = 1):
- We compare the current element
1against the sum of the element and the previous running sum:Math.max(1, currSum + 1) = Math.max(1, -2 + 1) = 1. - So,
currSum = 1(we discard the negative history and start a new subarray here). - Update global maximum:
maxSum = Math.max(maxSum, currSum) = Math.max(-2, 1) = 1.
- We compare the current element
- Step 3 (Index i = 2, Value = -3):
- Calculate new running sum:
Math.max(-3, currSum + (-3)) = Math.max(-3, 1 - 3) = -2. - So,
currSum = -2(we continue the subarray because-2is better than starting fresh with-3). - Update global maximum:
maxSum = Math.max(1, -2) = 1.
- Calculate new running sum:
- Step 4 (Index i = 3, Value = 4):
- Calculate new running sum:
Math.max(4, currSum + 4) = Math.max(4, -2 + 4) = 4. - So,
currSum = 4(we start a new subarray since4is greater than the accumulated2). - Update global maximum:
maxSum = Math.max(1, 4) = 4.
- Calculate new running sum:
- Step 5 (Index i = 4, Value = -1):
- Calculate new running sum:
Math.max(-1, 4 - 1) = 3. So,currSum = 3. - Update global maximum:
maxSum = Math.max(4, 3) = 4.
- Calculate new running sum:
- Step 6 (Index i = 5, Value = 2):
- Calculate new running sum:
Math.max(2, 3 + 2) = 5. So,currSum = 5. - Update global maximum:
maxSum = Math.max(4, 5) = 5.
- Calculate new running sum:
- Step 7 (Index i = 6, Value = 1):
- Calculate new running sum:
Math.max(1, 5 + 1) = 6. So,currSum = 6. - Update global maximum:
maxSum = Math.max(5, 6) = 6.
- Calculate new running sum:
- Step 8 (Index i = 7, Value = -5):
- Calculate new running sum:
Math.max(-5, 6 - 5) = 1. So,currSum = 1. - Update global maximum:
maxSum = Math.max(6, 1) = 6.
- Calculate new running sum:
- Step 9 (Index i = 8, Value = 4):
- Calculate new running sum:
Math.max(4, 1 + 4) = 5. So,currSum = 5. - Update global maximum:
maxSum = Math.max(6, 5) = 6.
- Calculate new running sum:
- Step 10 (Completion): The iteration completes, and the maximum subarray sum recorded is
6(corresponding to the segment[4, -1, 2, 1]).
Key Code Snippets & Explanations
Here is why the main logic in the solution is important:
currSum = Math.max(nums[i], currSum + nums[i]): This is the core dynamic programming state transition. It decides whether the current element should extend the previous subarray or start a new one.maxSum = Math.max(maxSum, currSum): This ensures we capture the peak value ofcurrSumat any point during the single-pass traversal.
Java Implementation Code
Below is the complete, self-contained Java source code that solves this problem. It also includes a
main method that traces the execution with console outputs.
package io.practise.dsa;
import java.util.Arrays;
public class MaximumSubarray {
// Brute Force - O(n^2)
public int maxSubArrayBrute(int[] nums) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
int sum = 0;
for (int j = i; j < nums.length; j++) {
sum += nums[j];
max = Math.max(max, sum);
}
}
return max;
}
// Optimized - O(n) (Kadane's Algorithm)
public int maxSubArray(int[] nums) {
int maxSum = nums[0], currSum = nums[0];
for (int i = 1; i < nums.length; i++) {
currSum = Math.max(nums[i], currSum + nums[i]);
maxSum = Math.max(maxSum, currSum);
}
return maxSum;
}
public static void main(String[] args) {
MaximumSubarray solver = new MaximumSubarray();
int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println("--- Maximum Subarray (Kadane's) Demonstration ---");
System.out.println("Input Array: " + Arrays.toString(nums));
System.out.println("\nExecuting Kadane's Algorithm...");
int maxSum = nums[0], currSum = nums[0];
System.out.printf("Initial state: currSum = %d, maxSum = %d\n", currSum, maxSum);
for (int i = 1; i < nums.length; i++) {
currSum = Math.max(nums[i], currSum + nums[i]);
maxSum = Math.max(maxSum, currSum);
System.out.printf("After element %d (val=%d): currSum = %d, maxSum = %d\n",
i, nums[i], currSum, maxSum);
}
System.out.println("Maximum contiguous subarray sum: " + solver.maxSubArray(nums));
}
}
Conclusion
Solving the Maximum Subarray (Kadane's Algorithm) problem highlights how we can leverage key data structures and algorithmic paradigms (like two pointers, hash maps, or dynamic programming) to significantly optimize runtime and simplify code complexity.