The Quest Begins (The "Why")
I still remember the first time I saw a coding interview question that asked for the maximum sum of a contiguous subarray. My brain went straight to the brute‑force playbook: try every start index, every end index, keep the best sum. O(n²) felt okay for tiny arrays, but the moment the interviewer slipped in a test with 10⁵ elements, my solution timed out like a rookie Jedi facing a Sith Lord without a lightsaber. I felt stuck, frustrated, and honestly a little embarrassed — why couldn’t I just see the pattern?
That moment sparked a quest: find a technique that could turn that O(n²) nightmare into a linear‑time hero. I dug into dynamic programming, not because I wanted to memorize a formula, but because I wanted to understand the why behind the magic. And that’s where the real power lives.
The Revelation (The Insight)
Dynamic programming shines when a problem has two properties: optimal substructure and overlapping subproblems. For the maximum subarray sum, the optimal substructure is simple: the best subarray ending at position i either is just the element a[i] or it’s the best subarray ending at i‑1 extended by a[i].
Think of it like deciding whether to keep walking on the same path or to step onto a new trail. If the path you’ve been on is already dragging you down (its sum is negative), you’re better off dropping it and starting fresh at the current stone. If it’s still helpful, you keep adding.
That decision — “extend or restart?” — is the heart of Kadane’s algorithm. We keep two variables while scanning the array once:
-
current– the best sum of a subarray that must end at the current index. -
best– the maximum of allcurrentvalues we’ve seen so far.
When we see a new number x, we ask: Is it better to start a new subarray at x, or to extend the previous one? Mathematically, that’s current = max(x, current + x). Then we update best = max(best, current).
Why does this give the optimal answer? Because every possible subarray has a first element where it starts. At that point, the algorithm considers starting fresh (current = x). From there, it greedily extends as long as doing so improves the sum. If extending ever makes the sum worse than starting anew, the algorithm resets, exactly mirroring the choice a human would make when they realize the current trail is no longer worth following. Since we examine each index once, we guarantee we’ve evaluated the optimal start point for every possible subarray — no missed chances, no redundant work.
Wielding the Power (Code & Examples)
The Struggle: Brute Force
def max_subarray_bruteforce(nums):
n = len(nums)
best = float('-inf')
for i in range(n):
for j in range(i, n):
cur = sum(nums[i:j+1])
if cur > best:
best = cur
return best
Two nested loops, plus a sum inside the inner loop → O(n³) if you’re not careful, or O(n²) with a running total. It works for tiny inputs, but it feels like swinging a wooden stick at a dragon.
The Victory: Kadane’s Algorithm (O(n))
def max_subarray_kadane(nums):
# Edge case: empty list (not expected in interview, but safe)
if not nums:
return 0
current = best = nums[0] # start with the first element
for x in nums[1:]:
# Either extend the previous subarray or start fresh at x
current = max(x, current + x)
best = max(best, current)
return best
Common traps
-
All‑negative arrays – If you initialise
currentto0, you’ll incorrectly return0(the empty subarray) when the answer should be the largest (least negative) element. Starting withnums[0]avoids that. -
Forgetting to update
bestinside the loop – You might thinkbestonly needs a final check, but the maximum could appear mid‑scan. -
Mixing up the order – Updating
bestbefore computing the newcurrentwould use stale data.
Let’s see it in action on a classic interview problem.
Problem 1: Maximum Subarray (LeetCode 53)
Input: [-2,1,-3,4,-1,2,1,-5,4]
Our algorithm walks through:
| i | x | current (max(x, current+x)) | best |
|---|---|---|---|
| 0 | -2 | -2 | -2 |
| 1 | 1 | max(1, -2+1=-1) → 1 | 1 |
| 2 | -3 | max(-3, 1-3=-2) → -2 | 1 |
| 3 | 4 | max(4, -2+4=2) → 4 | 4 |
| 4 | -1 | max(-1, 4-1=3) → 3 | 4 |
| 5 | 2 | max(2, 3+2=5) → 5 | 5 |
| 6 | 1 | max(1, 5+1=6) → 6 | 6 |
| 7 | -5 | max(-5, 6-5=1) → 1 | 6 |
| 8 | 4 | max(4, 1+4=5) → 5 | 6 |
Result: 6, which corresponds to subarray [4,-1,2,1].
Problem 2: Best Time to Buy and Sell Stock (LeetCode 121)
Here we want the maximum profit from a single buy‑sell pair. If we transform the price array into daily differences (prices[i] - prices[i-1]), the problem becomes exactly the maximum subarray sum on those differences.
def max_profit(prices):
if len(prices) < 2:
return 0
# compute differences on the fly
current = best = 0
for i in range(1, len(prices)):
diff = prices[i] - prices[i-1]
current = max(0, current + diff) # we never want a negative profit
best = max(best, current)
return best
Same O(n) pass, same intuition: either keep holding the stock (extend) or sell and start a new transaction (restart).
Why This New Power Matters
Now you’ve got a lightsaber that cuts through O(n²) brute force in a single sweep. Any interview that hides a “pick the best contiguous chunk” — whether it’s sums, profits, or even counting consecutive wins — can be tackled with this pattern. You’ll recognize the optimal‑substructure clue, write the two‑variable update, and walk away with confidence.
More than that, you’ve internalized the why: dynamic programming isn’t a bag of tricks; it’s a mindset of breaking a problem into decisions that depend only on the immediate past. Once you see that, other DP problems (knapsack, edit distance, etc.) start to feel like variations on the same theme.
Your Turn
Grab a piece of paper (or your favorite IDE) and try this: given an array of integers, find the maximum sum of a subarray with at least one negative number forced to be included. How would you tweak the Kadane loop to honor that constraint?
Drop your solution in the comments, share your “aha!” moment, and let’s keep leveling up together — Jedi style. 🚀
Top comments (0)