Maximum Subarray
Medium
💭 Introduction
You’re given an array of integers (which can be positive, negative, or zero). Your task is to find the contiguous subarray with the largest sum.
A subarray is a continuous part of an array.
For example, in [1, 2, 3], the subarrays are: [1], [2], [3], [1,2], [2,3], and [1,2,3].
Your goal is to find which contiguous subarray has the maximum sum.
🧩 Problem Statement
LeetCode #53 — Maximum Subarray
Given an integer array nums, find the subarray with the largest sum, and return its sum. A subarray is a contiguous non-empty sequence of elements within an array.
🔍 Example 1:
Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation:
The subarray [4,-1,2,1] has the largest sum = 6.
🔍 Example 2:
Input: nums = [1]
Output: 1
Explanation:
The subarray [1] has the largest sum = 1.
🔍 Example 3:
Input: nums = [5,4,-1,7,8]
Output: 23
Explanation:
The subarray [5,4,-1,7,8] has the largest sum = 23.
🧠 Step 1: Brute Force Intuition
The most straightforward approach is to check every possible subarray:
def maxSubArray(nums):
max_sum = float('-inf')
n = len(nums)
for i in range(n):
for j in range(i, n):
# Calculate sum of subarray from i to j
current_sum = sum(nums[i:j+1])
max_sum = max(max_sum, current_sum)
return max_sum
This checks all possible subarrays and keeps track of the maximum sum.
⏱️ Time Complexity: $O(n^3)$ (or $O(n^2)$ if we optimize the sum calculation)
📦 Space Complexity: $O(1)$
Problem: This is too slow for large arrays!
🤔 Step 2: The Key Insight
Here’s the crucial observation:
At each position
i, you have two choices:
- Extend the previous subarray by including
nums[i]- Start fresh from
nums[i]
When should you start fresh? When the previous sum becomes negative!
Why? Because adding a negative sum to the current element will only make it smaller.
For example:
- If previous sum = -5 and current element = 3
- Extending: -5 + 3 = -2
- Starting fresh: 3
- Starting fresh is better!
This leads us to Kadane’s Algorithm.
🚀 Step 3: Kadane’s Algorithm (The Elegant Solution)
At each position, we decide:
current_sum = max(nums[i], current_sum + nums[i])
This means: “Should I start a new subarray here, or extend the previous one?”
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_sum = nums[0]
current_sum = nums[0]
for i in range(1, len(nums)):
# Either extend previous subarray or start new
current_sum = max(nums[i], current_sum + nums[i])
# Update global maximum
max_sum = max(max_sum, current_sum)
return max_sum
Let’s trace through Example 1: [-2,1,-3,4,-1,2,1,-5,4]
| i | nums[i] | current_sum | max_sum | Decision |
|---|---|---|---|---|
| 0 | -2 | -2 | -2 | Start |
| 1 | 1 | 1 | 1 | Start fresh (1 > -2+1) |
| 2 | -3 | -2 | 1 | Extend (1-3 = -2) |
| 3 | 4 | 4 | 4 | Start fresh (4 > -2+4) |
| 4 | -1 | 3 | 4 | Extend (4-1 = 3) |
| 5 | 2 | 5 | 5 | Extend (3+2 = 5) |
| 6 | 1 | 6 | 6 | Extend (5+1 = 6) |
| 7 | -5 | 1 | 6 | Extend (6-5 = 1) |
| 8 | 4 | 5 | 6 | Extend (1+4 = 5) |
Final answer: 6 (subarray [4,-1,2,1])
⏱️ Time Complexity: $O(n)$ — Single pass through the array
📦 Space Complexity: $O(1)$ — Only two variables
🎯 Step 4: Understanding as Dynamic Programming
Kadane’s Algorithm is actually a DP solution! Let’s formalize it:
Define: dp[i] = maximum sum of subarray ending at index i
Recurrence relation:
dp[i] = max(nums[i], dp[i-1] + nums[i])
Base case:
dp[0] = nums[0]
Answer:
max(dp[0], dp[1], ..., dp[n-1])
Here’s the explicit DP version:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
dp = [0] * n
dp[0] = nums[0]
max_sum = dp[0]
for i in range(1, n):
dp[i] = max(nums[i], dp[i-1] + nums[i])
max_sum = max(max_sum, dp[i])
return max_sum
⏱️ Time: $O(n)$
📦 Space: $O(n)$
🧮 Step 5: Space Optimization
Notice that dp[i] only depends on dp[i-1]. We don’t need the entire array!
This is exactly what we did in Step 3 with current_sum:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_sum = current_sum = nums[0]
for num in nums[1:]:
current_sum = max(num, current_sum + num)
max_sum = max(max_sum, current_sum)
return max_sum
This is the optimal solution — Kadane’s Algorithm with O(1) space.
⏱️ Time Complexity: $O(n)$
📦 Space Complexity: $O(1)$
🔄 Step 6: Alternative Formulation
Some prefer this equivalent formulation:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_sum = float('-inf')
current_sum = 0
for num in nums:
current_sum += num
max_sum = max(max_sum, current_sum)
# If current_sum becomes negative, reset to 0
if current_sum < 0:
current_sum = 0
return max_sum
This version explicitly resets when the sum becomes negative, which is equivalent to starting fresh.
🎯 Key Insights
-
Greedy + DP: Kadane’s Algorithm is both greedy (local decision) and DP (optimal substructure).
-
The decision rule: At each position, decide whether to extend or start fresh based on whether the previous sum helps or hurts.
-
Negative numbers: The algorithm handles negative numbers naturally — if all numbers are negative, it returns the least negative one.
-
Optimal substructure: The maximum subarray ending at position
idepends only on the maximum subarray ending at positioni-1.
📊 Complexity Summary
| Approach | Time | Space |
|---|---|---|
| Brute Force | $O(n^3)$ or $O(n^2)$ | $O(1)$ |
| DP with Array | $O(n)$ | $O(n)$ |
| Kadane’s Algorithm | $O(n)$ | $O(1)$ |
🌟 Follow-up: Finding the Actual Subarray
If you need to return the subarray itself (not just the sum):
class Solution:
def maxSubArray(self, nums: List[int]) -> tuple:
max_sum = current_sum = nums[0]
start = end = 0
temp_start = 0
for i in range(1, len(nums)):
if nums[i] > current_sum + nums[i]:
current_sum = nums[i]
temp_start = i
else:
current_sum = current_sum + nums[i]
if current_sum > max_sum:
max_sum = current_sum
start = temp_start
end = i
return max_sum, nums[start:end+1]
💡 Related Problems
- House Robber - Similar DP pattern
- Maximum Product Subarray - Variant with multiplication
- Best Time to Buy and Sell Stock - Similar greedy approach
- Maximum Sum Circular Subarray - Circular array variant
🏆 Why This Problem Matters
- Interview favorite: One of the most common DP problems
- Pattern recognition: Teaches the “extend vs. start fresh” pattern
- Optimization: Shows how DP can be space-optimized
- Real-world applications: Stock trading, signal processing, data analysis