Coding Interview Patterns Module 24 – Dynamic programming: foundations
Maximum subarray: sum, product and circular
The stretch of consecutive values with the largest total: the maximum subarray sum, the maximum product with negatives and zeros, and the circular case.
What you will learn
- Compute the maximum subarray sum in one pass with Kadane's algorithm
- Prove the algorithm with an invariant about the best stretch ending at each position
- Report the days of the best stretch, and handle arrays where every value is negative
- Compute the maximum product by carrying both the largest and the smallest product
- Solve the circular version, where a stretch may wrap around the end
Before you start
What you’ll learn · Before you start
What you will learn
- Compute the maximum subarray sum in one pass with Kadane's algorithm
- Prove the algorithm with an invariant about the best stretch ending at each position
- Report the days of the best stretch, and handle arrays where every value is negative
- Compute the maximum product by carrying both the largest and the smallest product
- Solve the circular version, where a stretch may wrap around the end
Before you start
On this page
The maximum subarray problem asks for the stretch of consecutive values with the largest total, such as the best run of days in a shop’s profit-and-loss ledger. Kadane’s algorithm solves it in one pass by keeping the best total of a stretch that ends at the current position, which is why it belongs with linear DP. Two variants come up often. The maximum product of a stretch changes what must be kept, because a negative value turns the smallest product into the largest and a zero ends every stretch through it. The circular version lets a stretch wrap from the last value to the first. This lesson poses all three on original data.
Three problems on one row of numbers
Problem A, the best stretch of a ledger. A shop records each day’s profit or loss. Return the largest total of a
non-empty stretch of consecutive days. Limits: 1 ≤ n ≤ 200,000 and −10,000 ≤ ledger[i] ≤ 10,000.
[3, −5, 4, −1, 2, 6, −8, 1]gives 11, from days 2 to 5.[−7, −2, −9]gives −2: a loss on every day, so the best stretch is the single smallest loss.
Problem B, the best product of a run of cards. A row of up to 200,000 number cards from −9 to 9; return the largest product of a run of consecutive cards. Every product of a run fits in a signed 64-bit integer.
[2, −3, −2, 4, 0, −5]gives 48, from the first four cards.[−2, 0, −1]gives 0, the 0 on its own.
Problem C, a ledger in a ring. The same as A, but the last day is followed by the first, so a stretch may wrap.
[5, −8, 2, 4, −9, 6] gives 11, from the last day and the first: 6 + 5.
The three problems, with the best stretch of each
Text description of the diagram
The diagram has three rows of boxes, one number per box. The boxes of each row's best stretch have a double border.
- The ledger 3, −5, 4, −1, 2, 6, −8, 1. The best stretch is 4, −1, 2, 6 (days 2 to 5), with a total of 11. It includes the loss of 1, because the profits on both sides of it are larger.
- The cards 2, −3, −2, 4, 0, −5. The best run for a product is 2, −3, −2, 4 (cards 0 to 3), with a product of 48: the two negative cards cancel out. The 0 ends every run that crosses it.
- The ledger 5, −8, 2, 4, −9, 6, read as a ring: a dashed link runs from the last day back to the first. The best stretch wraps from the last day to the first: 6 and then 5, with a total of 11. Without wrapping, the best stretch would be 2, 4 or the single 6, a total of 6.
The brute force, and why it fails
Try every start, and extend the stretch one value at a time while keeping its total or product:
def best_sum_brute(ledger):
"""Every stretch of consecutive days, its total kept as the end moves right: n(n + 1) / 2 stretches."""
best, stretches = None, 0
for start in range(len(ledger)):
total = 0
for end in range(start, len(ledger)):
total += ledger[end]
stretches += 1
best = total if best is None else max(best, total)
return best, stretches
def best_product_brute(cards):
"""The same for products: every run of consecutive cards, its product kept as the run grows."""
best = None
for start in range(len(cards)):
product = 1
for end in range(start, len(cards)):
product *= cards[end]
best = product if best is None else max(best, product)
return best
for ledger in ([3, -5, 4, -1, 2, 6, -8, 1], [-7, -2, -9]):
best, stretches = best_sum_brute(ledger)
print(f"ledger {ledger}: best stretch {best} ({stretches} stretches tried)")
for cards in ([2, -3, -2, 4, 0, -5], [-2, 0, -1], [-3]):
print(f"cards {cards}: best product {best_product_brute(cards)}")
n = 200_000
print(f"{n:,} days: {n * (n + 1) // 2:,} stretches") Output
ledger [3, -5, 4, -1, 2, 6, -8, 1]: best stretch 11 (36 stretches tried) ledger [-7, -2, -9]: best stretch -2 (6 stretches tried) cards [2, -3, -2, 4, 0, -5]: best product 48 cards [-2, 0, -1]: best product 0 cards [-3]: best product -3 200,000 days: 20,000,100,000 stretches
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stretch_brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
There are n(n + 1)/2 stretches: 36 for the first ledger, but 20,000,100,000 for 200,000 days, minutes of work even in C++. Recomputing each sum from scratch would cost another factor of n.
What to notice
- A stretch that ends on day i either extends the best stretch ending on day i − 1 or starts on day i. One number per day is enough to decide between them.
- For products, a negative card turns the smallest product into the largest, so the smallest must be carried too, and a 0 ends every run that crosses it.
- A stretch that wraps around leaves out one stretch in the middle, so the circular best is tied to the worst stretch.
Kadane’s algorithm, and the invariant that proves it Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- The days of the best stretch, traced
- Ledgers of losses only: the stretch must not be empty
- Products: carry the largest and the smallest
- A weaker promise: when only the answer fits in 64 bits
- Around the season: the total minus the worst stretch
- The code in five languages
- Four bugs and the smallest inputs that break them
- Exercise
- The best stretch of a ledger that wraps around
- Check yourself
- A practice set with sample answers
- Interview questions
- Prove that Kadane's algorithm returns the largest total of a non-empty stretch, using an invariant. Then say what changes when the problem allows the empty stretch, and what a common version returns on an array where every value is negative.
- For the maximum product of a run, the statement promises only that the answer fits in a signed 64-bit integer. Show how the usual solution that carries the largest and the smallest product can return a wrong answer in Java or Go, and fix it without arbitrary-precision numbers.
- Find the best stretch of a circular array, where a stretch may wrap from the last value to the first, in one pass. Prove your method, say which inputs need a special case, and explain how to return the start and the length of the best stretch.
- Key takeaways
8 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 23 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Programming pearls: algorithm design techniques (J. Bentley, Communications of the ACM 27(9), 1984) (Association for Computing Machinery)
- The Java Language Specification, Java SE 25: 15.17.1 Multiplication operator * (Oracle)
- Math.multiplyExact (Java SE 25 API) (Oracle)
- The Go Programming Language Specification: Integer overflow (The Go Authors)
- Arithmetic operators: overflows (cppreference.com) (cppreference.com)
- ECMAScript 2026 Language Specification: Number.MAX_SAFE_INTEGER (Ecma International)
- ECMAScript 2026 Language Specification: The BigInt type (Ecma International)
- UndefinedBehaviorSanitizer (Clang documentation) (The LLVM Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress