53. Maximum Subarray
Quick Identifier
- Topic: arrays-hashing
- Pattern: Kadane's Algorithm (Greedy / DP-1D)
- Hint: Does adding the current number to your running total make your situation worse than just starting a brand new running total from the current number itself?
- Difficulty: Medium
- Companies: Amazon, Google, Meta, Microsoft, LinkedIn
- LeetCode: 53
Quick Sights
- Time Complexity:
O(n). We iterate through the array exactly once. - Space Complexity:
O(1). We only maintain two integer variables (currentSumandmaxSum). - Core Mechanism: As you iterate, maintain a running
currentSum. For every new element, you have a greedy choice: either add the element to your existingcurrentSum, or throw away thecurrentSumentirely and start a new sum beginning with this element. You make this choice by checking which option yields a higher value. Keep track of themaxSumseen at any point.
Concept Explanation
As a senior engineer, Kadane's algorithm is the canonical example of dynamic programming collapsed into a greedy choice.
- The Toxic Asset: Imagine you are acquiring assets (the numbers in the array). Some are profitable (positive), some are toxic debt (negative).
- The Clean Slate: If your running total of assets ever drops below 0, your entire collection has become a net liability. If the next number in the array is
5, why would you add it to a negative running total (e.g.,-2 + 5 = 3)? You are instantly better off throwing away all previous assets, declaring bankruptcy, and just taking the5by itself (0 + 5 = 5). - The Core Invariant: At index
i, the maximum subarray sum ending exactly atiis either the elementnums[i]by itself, ornums[i]added to the maximum subarray sum ending ati-1.currentSum = Math.max(nums[i], currentSum + nums[i])
Diagram
This flowchart outlines the greedy decision matrix of Kadane's Algorithm.
Loading diagram…
Study Pattern (SDE3+)
- 0–3 min: Recognize Kadane's instantly. It is the definitive $O(n)$ time / $O(1)$ space solution. Write out the recurrence relation
dp[i] = max(nums[i], dp[i-1] + nums[i])and explain how you optimize away thedparray into a singlecurrentSuminteger. - Implementation pass: Initialize both
currentSumandmaxSumtonums[0]. Do not initialize them to0, because if the array contains entirely negative numbers (e.g.,[-3, -5, -2]), initializing to0will incorrectly return0instead of-2. - 5 min extension: What if the interviewer asks you to return the start and end indices of the maximum subarray, not just the sum? You would track two extra variables:
tempStartandbestStart. WhenevercurrentSumresets tovalue, you settempStart = index. WhenevermaxSumis updated, you setbestStart = tempStartandbestEnd = index.
Approaches
- Brute force — Check the sum of every possible subarray. Time: $O(n^2)$ (or $O(n^3)$ if implemented very naively). Space: $O(1)$.
- Divide and Conquer — Split the array in half, find the max in the left, max in the right, and max crossing the middle. Time: $O(n \log n)$, Space: $O(\log n)$ call stack. This is rarely the desired answer, but it's a common follow-up question.
- Kadane's Algorithm — DP optimized to $O(1)$ space. Time: $O(n)$, Space: $O(1)$. (Always pick this)
Optimal Solution
Go
func maxSubArray(nums []int) int {
currentSum := nums[0]
maxSum := nums[0]
for i := 1; i < len(nums); i++ {
value := nums[i]
// Greedy choice: extend the existing subarray, or start a new one?
if currentSum+value > value {
currentSum += value
} else {
currentSum = value
}
// Update the global maximum if necessary
if currentSum > maxSum {
maxSum = currentSum
}
}
return maxSum
}JavaScript
function maxSubArray(nums) {
let currentSum = nums[0];
let maxSum = nums[0];
for (let i = 1; i < nums.length; i++) {
const value = nums[i];
// Math.max succinctly expresses the greedy choice
currentSum = Math.max(value, currentSum + value);
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}Walkthrough
Input: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
| index | value | currentSum + value | Choice (max) | currentSum | maxSum | Action |
|---|---|---|---|---|---|---|
| Initial | - | - | - | -2 | -2 | nums[0] |
| 1 | 1 | -2 + 1 = -1 | max(-1, 1) | 1 | 1 | Reset subarray. |
| 2 | -3 | 1 + -3 = -2 | max(-2, -3) | -2 | 1 | Extend. |
| 3 | 4 | -2 + 4 = 2 | max(2, 4) | 4 | 4 | Reset subarray. |
| 4 | -1 | 4 + -1 = 3 | max(3, -1) | 3 | 4 | Extend. |
| 5 | 2 | 3 + 2 = 5 | max(5, 2) | 5 | 5 | Extend. Max updated. |
| 6 | 1 | 5 + 1 = 6 | max(6, 1) | 6 | 6 | Extend. Max updated. |
| 7 | -5 | 6 + -5 = 1 | max(1, -5) | 1 | 6 | Extend. |
| 8 | 4 | 1 + 4 = 5 | max(5, 4) | 5 | 6 | Extend. |
Output: 6 (coming from subarray [4, -1, 2, 1])
Edge Cases
- All negative numbers (e.g.,
[-3, -5, -2]). Will correctly return-2. - Single element array. Will correctly return that element.
- All positive numbers. Will naturally sum the entire array.
Pitfalls
- Initializing
maxSum = 0. This completely breaks if the array contains only negative numbers. Always initialize tonums[0]. - Attempting a sliding window with a
leftpointer. Kadane's is conceptually simpler; you don't need to manage a trailing pointer unless you are asked to return the specific indices of the subarray.
Similar Problems
- 152. Maximum Product Subarray — Kadane's variant tracking both min and max to handle negative multiplications.
- 121. Best Time to Buy and Sell Stock — Can be solved identically if you map the array to daily price deltas.
- 918. Maximum Sum Circular Subarray — Kadane's extended to a wrap-around array.
Mind-Map Tags
#kadane #dynamic-programming #greedy #subarray-sum #optimal-substructure
Mark this page when you finish learning it.
Last updated on
Spotted something unclear or wrong on this page?