This deck works the maximum sum subarray problem three ways: by brute force, by divide-and-conquer including the crossing subarray case, and by Kadane's linear scan. It targets confusing a subarray with a subsequence, forgetting the crossing case in divide-and-conquer, resetting Kadane's running sum to zero on an all-negative array, and off-by-one errors when reporting the start and end indices. Every trace is re-derived by hand on the same running example, so all three methods check against each other.
Subject: CS3000 Algorithms · 131 slides · symbolic lesson
Open the interactive version of this deck · Homework for this lesson
Title
CS3000 Algorithms
Three ways to find the best contiguous slice — and why the fastest one is also the simplest.
Objectives
By the end you can:
Warm-up
Discussion prompt
Before we open Maximum Sum Subarray: without looking back, what was the main idea of Quicksort, Selection & Median of Medians, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
That deck covers quicksort's partition step and its recursion, the best, average, and worst-case running times, and the sorted input that triggers the worst case. It then explains randomized pivots and why they make the bad case vanishingly unlikely, introduces the selection problem and quickselect, and gives the median-of-medians pivot rule, using groups of five, that guarantees linear worst-case selection. It targets the myths that quicksort is always n log n, that selection requires a full sort, and that the choice of pivot does not matter for the worst case, and it explains why groups of five in particular are used.
Concept
Before any new material: cover the screen.
You have named 11 reusable moves so far. Say as many as you can out loud, by number, from memory.
Do not advance until you have actually tried. Getting four of eight is information; skipping the exercise is not.
Here they are. Score yourself.
Today adds one move to this list. Everything else you will need is already above.
The question that starts every proof from here on is not how do I begin. It is which of these applies here?
Counterexample
Discussion prompt
You have named 11 reusable moves so far. Say as many as you can out loud, by number, from memory.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Do not advance until you have actually tried. Getting four of eight is information; skipping the exercise is not.
Section
Section 1
Concept
A subarray is a run of elements taken from an array that are all next to each other, with nothing skipped in between. You pick a starting position and an ending position, and the subarray is everything from one to the other.
subarray — A contiguous slice of an array: a starting position and an ending position, plus every element in between. No gaps allowed.
Picture it
Animation
Shows: Three algorithms, three curves — a rendered Manim animation.
Rendered with Manim.
Takeaway: Same answer from all three. Only the cost differs.
Intuition
Line the array's elements up in a row, like beads on a string. A subarray is what you get by cutting the string at two points and keeping the piece in between — you cannot pull individual beads out from the middle and skip over the rest.
That single restriction — no skipping — is what makes this problem different from picking any collection of elements you like.
Concept
Given an array of numbers (some positive, some negative), find the contiguous subarray whose elements add up to the largest possible sum.
The subarray must be non-empty — you must pick at least one element. An empty selection is not a valid answer, even though its sum would trivially be zero.
\[ \text{Find } i \le j \text{ maximizing } \sum_{k=i}^{j} A[k] \]
Analogy
Discussion prompt
Explain The maximum sum subarray problem by analogy to something with no CS3000 Algorithms in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Given an array of numbers (some positive, some negative), find the contiguous subarray whose elements add up to the largest possible sum.
Concept
This lesson uses one array throughout, so you can check every method against the same answer.
\[ A = [\,-2,\ 1,\ -3,\ 4,\ -1,\ 2,\ 1,\ -5,\ 4\,] \]
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| value A[index] | -2 | 1 | -3 | 4 | -1 | 2 | 1 | -5 | 4 |
Picture it
Figure (svg): Nine boxes in a row showing the array negative two, one, negative three, four, negative one, two, one, negative five, four, with the four boxes for four, negative one, two, one highlighted as the best-looking run.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
Before building any algorithm, just look at the array. Somewhere in there is one run of numbers that beats every other run. Try to spot it.
Intuition
Before building any algorithm, just look at the array. Somewhere in there is one run of numbers that beats every other run. Try to spot it.
Figure (svg): Nine boxes in a row showing the array negative two, one, negative three, four, negative one, two, one, negative five, four, with the four boxes for four, negative one, two, one highlighted as the best-looking run.
That highlighted run does look promising. We will prove, three separate ways, exactly why it wins — and get the same answer every time.
Explain it
Discussion prompt
Explain Sneak preview: eyeballing the best slice to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Before building any algorithm, just look at the array. Somewhere in there is one run of numbers that beats every other run. Try to spot it.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student wants a big sum, so they pick whichever elements look large, skipping the ones that hurt, regardless of position.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: This selection skips indices 1, 2, 4, 6, and 7 entirely.
A valid subarray keeps every position between the start and the end — no skipping, even if some in-between elements are negative.
Why: This selection skips indices 1, 2, 4, 6, and 7 entirely. Skipping around like this makes it a subsequence, not a subarray — the problem does not allow it.
Trap
A student wants a big sum, so they pick whichever elements look large, skipping the ones that hurt, regardless of position.
\[ \text{picked indices } 0, 3, 5, 8:\ -2,\ 4,\ 2,\ 4 \]
Add up the cherry-picked elements
Why: This selection skips indices 1, 2, 4, 6, and 7 entirely. Skipping around like this makes it a subsequence, not a subarray — the problem does not allow it.
\[ -2 + 4 + 2 + 4 = 8 \quad (\text{not a valid answer}) \]
A valid subarray keeps every position between the start and the end — no skipping, even if some in-between elements are negative.
\[ \text{indices } 3 \text{ through } 6:\ 4,\ -1,\ 2,\ 1 \]
Add up the contiguous run instead
Why: Every index from 3 to 6 is included, with nothing skipped. That is what makes it a legal subarray, and its sum of 6 is exactly the answer we will confirm three different ways.
\[ 4 + (-1) + 2 + 1 = 6 \]
Notation
Annotate
From Trap: a subarray is not a subsequence — read this one piece at a time. What is each part doing?
On: \( \text{picked indices } 0, 3, 5, 8:\ -2,\ 4,\ 2,\ 4 \)
Intuition
Nine numbers are easy to scan by eye. A real array might have a million numbers, and the best run could start and end anywhere.
We need a systematic procedure that is guaranteed to find the best run, no matter how big the array gets, without relying on a human noticing the right pattern.
Concept
We will solve the same problem three times, each version faster than the last, and check that all three agree on our running example.
Section
Section 2
Concept
The most direct plan: generate every possible subarray, add up each one, and remember the biggest sum seen so far.
A subarray is fully determined by two choices: where it starts and where it ends. So we need a loop over every possible start, and for each start, a loop over every possible end.
Picture it
Animation
Shows: The all-negative edge case — a rendered Manim animation.
Rendered with Manim.
Takeaway: Decide up front whether the empty subarray counts. The two answers differ.
Intuition
Picture two markers on the number line of positions: a start marker and an end marker, with the start marker never to the right of the end marker.
The outer loop drags the start marker across every position. For each place the start marker stops, the inner loop drags the end marker across every position from the start onward.
Concept
The most naive version adds up every element between the start and end markers from scratch, every single time the end marker moves.
That inner addition is itself a loop. Start loop, end loop, and a summing loop nested inside — three loops stacked on top of each other.
\[ \text{start loop} \times \text{end loop} \times \text{sum loop} \ \Rightarrow\ \Theta(n^3) \]
Concept
Notice that when the end marker moves one step to the right, the new sum is just the old sum plus the one new element — there is no need to re-add everything from the start.
running sum — A single accumulating total that is updated by adding one new element at a time, instead of being recomputed from scratch.
\[ \text{sum}(i, j+1) = \text{sum}(i, j) + A[j+1] \]
Ranking
Put in order
Put the moves of Counting subarrays: the pattern into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. For size 1 there is only 1 subarray (the single element itself).
Worked example
Before tracing sums, notice how many subarrays even exist. For an array of a given size, count every choice of start and end.
List the count for small sizes
Why: For size 1 there is only 1 subarray (the single element itself). For size 2 there are 3: two single elements plus the whole pair.
| array size | number of subarrays |
|---|---|
| 1 | 1 |
| 2 | 3 |
| 3 | 6 |
| 4 | 10 |
| 5 | 15 |
Spot the closed form
Why: Each row matches size times (size plus one), divided by two — the same formula for summing 1 through size.
\[ \text{count}(n) = \frac{n(n+1)}{2} \]
Verify against size 5
Why: Plugging n = 5 into the formula gives 5 times 6 divided by 2, which is 15 — exactly the row above. So the brute-force loop structure really does examine that many candidates.
\[ \frac{5 \cdot 6}{2} = 15\ \checkmark \]
Worked example
Trace the running-sum brute force on a short array so every step is visible.
\[ B = [\,4,\ -1,\ 2,\ 1,\ -5\,] \]
Fix the start marker at position 0 and slide the end marker rightward
Why: Each new sum adds just one more element to the previous running sum: 4, then 4 plus negative one is 3, then 3 plus 2 is 5, then 5 plus 1 is 6, then 6 plus negative five is 1.
| start | best end reached | best sum for this start |
|---|---|---|
| 0 | 3 | 6 |
Repeat for every remaining start position
Why: Slide the start marker to 1, 2, 3, and 4 in turn, and for each one slide the end marker across the rest of the array the same way.
| start | best end reached | best sum for this start |
|---|---|---|
| 1 | 3 | 2 |
| 2 | 3 | 3 |
| 3 | 3 | 1 |
| 4 | 4 | -5 |
Verify the overall winner across all five starts
Why: Comparing the best-sum column (6, 2, 3, 1, -5), the largest is 6, achieved starting at position 0 and ending at position 3 — the subarray 4, -1, 2, 1. That matches directly re-adding those four numbers.
\[ 4 + (-1) + 2 + 1 = 6\ \checkmark \]
Picture it
Animation
Shows: The obvious approach, and its cost — a rendered Manim animation.
Rendered with Manim.
Takeaway: Correct, and unusable past a few thousand elements.
Estimation
Predict first
Every algorithm in this lesson must handle the case where nothing in the array is positive. Try brute force first, since it needs no cleverness at all.
Commit before you compute: what does Brute force on an all-negative array come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Verify the overall winner is the least negative element
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Comparing -8, -3, -6, -2, -5, -4, the largest is -2.
Worked example
Every algorithm in this lesson must handle the case where nothing in the array is positive. Try brute force first, since it needs no cleverness at all.
\[ C = [\,-8,\ -3,\ -6,\ -2,\ -5,\ -4\,] \]
Extend each start marker and watch every running sum only get worse
Why: Adding another negative number never increases a running sum, so for every start position the best subarray is just that one single element by itself.
| start | best single-element sum |
|---|---|
| 0 | -8 |
| 1 | -3 |
| 2 | -6 |
| 3 | -2 |
| 4 | -5 |
| 5 | -4 |
Verify the overall winner is the least negative element
Why: Comparing -8, -3, -6, -2, -5, -4, the largest is -2. Since the subarray must be non-empty, -2 is the correct answer here — not zero.
\[ \max(-8,-3,-6,-2,-5,-4) = -2\ \checkmark \]
Picture it
Animation
Shows: Each line of the worked example "Brute force on an all-negative array", appearing one at a time.
The same working the example does, in the order a tutor would write it.
Takeaway: Comparing -8, -3, -6, -2, -5, -4, the largest is -2. Since the subarray must be non-empty, -2 is the correct answer here — not zero.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student's loop uses the end marker as a plain counter and reports it directly as the ending position, without checking what it actually points to.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: If the loop's end marker was really pointing one position past the last element actually included, reporting it directly includes one extra element that was never summed — the reported slice does not match the reported…
Always double-check what the last-updated marker actually points to: the last position included in the sum, not one past it.
Why: If the loop's end marker was really pointing one position past the last element actually included, reporting it directly includes one extra element that was never summed — the reported slice does not match the reported sum.
Trap
A student's loop uses the end marker as a plain counter and reports it directly as the ending position, without checking what it actually points to.
\[ \text{best sum found while end marker } = 4 \text{ (one past the last included element)} \]
Report start 0, end 4 as the winning subarray
Why: If the loop's end marker was really pointing one position past the last element actually included, reporting it directly includes one extra element that was never summed — the reported slice does not match the reported sum.
\[ \text{claims } A[0..4],\ \text{but the sum 6 only used } A[0..3] \]
Always double-check what the last-updated marker actually points to: the last position included in the sum, not one past it.
\[ \text{best sum 6 came from summing exactly } A[0], A[1], A[2], A[3] \]
Report start 0, end 3, and re-add to confirm
Why: Re-summing the reported range must reproduce the reported total exactly. Here 4 plus negative one plus 2 plus 1 is 6, matching the claimed sum, so the indices are trustworthy.
\[ A[0]+A[1]+A[2]+A[3] = 4-1+2+1 = 6\ \checkmark \]
Concept
With the running-sum trick, the summing loop disappears, leaving just the start loop and the end loop.
\[ \text{start loop} \times \text{end loop} \ \Rightarrow\ \Theta(n^2) \]
This is correct and simple, and it is the baseline every faster method must agree with. But quadratic growth becomes a real problem as the array grows.
Intuition
Imagine an array with one hundred thousand numbers in it — not unusual for real data.
\[ n = 100{,}000 \ \Rightarrow\ n^2 = 10{,}000{,}000{,}000 \text{ operations} \]
Ten billion operations is the kind of number that turns an instant answer into a coffee break. This is exactly the motivation for the faster methods that follow.
Section
Section 3
Concept
Split the array into a left half and a right half at a middle position, solve each half the same way, and then combine the two answers.
Name the middle position clearly: call it the midpoint. The left half runs from the start of the array up to and including the midpoint; the right half runs from just after the midpoint to the end.
\[ \text{midpoint} = \left\lfloor \frac{\text{first index} + \text{last index}}{2} \right\rfloor \]
Picture it
Figure (svg): The nine-element array split into a left half of five boxes and a right half of four boxes, with a dashed line at the midpoint boundary.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
Once the array is cut in half, the true best subarray has exactly three possible hiding places.
Intuition
Once the array is cut in half, the true best subarray has exactly three possible hiding places.
Figure (svg): The nine-element array split into a left half of five boxes and a right half of four boxes, with a dashed line at the midpoint boundary.
Picture it
Animation
Shows: The prefix-sum view — a rendered Manim animation.
Rendered with Manim.
Takeaway: A second route to linear time, and the one that generalises.
Concept
Take any subarray at all. Either every one of its positions is at or before the midpoint (case one), or every position is after the midpoint (case two), or it includes at least one position from each side (case three).
There is no fourth possibility, and no subarray can belong to more than one case at once. The three cases are exhaustive and mutually exclusive, so checking all three is both necessary and sufficient.
Sorting
Sort into buckets
These are the pieces of Maximum Sum Subarray, out of order. Put each one back under the part of the lesson it belongs to.
Concept
A crossing subarray is one that includes the midpoint position itself and the position right after it — it reaches across the cut.
crossing subarray — A subarray whose range includes both the last index of the left half and the first index of the right half, so it cannot be found by looking at either half alone.
Matching
Match the pairs
Match each term to the definition this lesson gave it — not the one you would guess from the word.
Why: These are the working definitions of subarray, running sum, crossing subarray as Maximum Sum Subarray uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Fill the middle
Fill in the blanks
From Process: the crossing case, first attempt — finish the line. Write what belongs on the right of the equals sign before you look.
\text\max_{\text{left piece}} + \max_{\text{right piece}} = ___
Why: Producing the right-hand side unprompted is the difference between recognising this line and being able to use it. That is the definition of crossing, so enumerate all such pairs and take the best.
Intuition
Watch me not know the answer. This is what the first two minutes actually look like.
We split the array at the middle. The best subarray is on the left, on the right, or crossing the split. The first two are recursive calls. The crossing one is not.
Try every pair: one endpoint left of the split, one right of it
Why: That is the definition of crossing, so enumerate all such pairs and take the best. Correct, and easy to write.
It costs too much
Why: There are about n/2 choices on each side, so n squared over 4 pairs — quadratic work in the combine step alone. The recurrence becomes T(n) = 2T(n/2) + n squared, which unrolls to quadratic. The divide and conquer bought nothing.
Dead end. Not a mistake — a move that was worth trying and did not pay off. This happens in most proofs.
Back up. The two halves of a crossing subarray are independent
Why: A crossing subarray is a left piece ending at the split, plus a right piece starting at the split. Maximize each separately with one scan apiece.
\[ \text{best crossing} = \max_{\text{left piece}} + \max_{\text{right piece}} \]
Two linear scans instead of a quadratic double loop. Notice the pivot came from re-reading the definition, not from a cleverer search.
The expert does not see the whole path in advance. The expert tries something, reads the result, and adjusts. That is the skill.
Translation
\( \text{best crossing} = \max_{\text{left piece}} + \max_{\text{right piece}} \)
Draw it
Translate both ways. First write the expression above as a sentence with no symbols in it at all. Then cover it, and write your sentence back as notation. If the two versions disagree, the disagreement is the thing to fix.
Intuition
A crossing subarray is built from two pieces glued together: a run ending exactly at the midpoint, and a run starting exactly at the next position.
Scan leftward from the midpoint, extending one step at a time and keeping the best running total — that finds the best possible left piece. Separately scan rightward from the next position the same way, for the best possible right piece. Add the two best pieces together.
\[ \text{best crossing} = \text{best suffix ending at midpoint} + \text{best prefix starting just after} \]
Step zero
Discussion prompt
Compute the crossing subarray on the running example — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Scan leftward from the midpoint to find the best suffix
Answer:
Worked example
Split our running example array A at its midpoint, between position 4 and position 5.
\[ \text{left half}: A[0..4] = [-2,1,-3,4,-1] \qquad \text{right half}: A[5..8] = [2,1,-5,4] \]
Scan leftward from the midpoint to find the best suffix
Why: Starting at position 4 and extending backward one step at a time, track the running total after each extension and keep the best one seen.
| suffix starting at | running total |
|---|---|
| 4 | -1 |
| 3 | 3 |
| 2 | 0 |
| 1 | 1 |
| 0 | -1 |
Scan rightward from just after the midpoint to find the best prefix
Why: Starting at position 5 and extending forward one step at a time, track the running total after each extension and keep the best one seen.
| prefix ending at | running total |
|---|---|
| 5 | 2 |
| 6 | 3 |
| 7 | -2 |
| 8 | 2 |
Verify by adding the two best pieces and re-summing directly
Why: The best suffix is 3 (positions 3 through 4), and the best prefix is 3 (positions 5 through 6), giving a crossing total of 6 for positions 3 through 6. Re-adding those four elements directly confirms the same total.
\[ 3 + 3 = 6; \quad A[3]+A[4]+A[5]+A[6] = 4-1+2+1 = 6\ \checkmark \]
Picture it
Animation
Shows: Kadane works on a stream — a rendered Manim animation.
Rendered with Manim.
Takeaway: Which the divide-and-conquer version cannot do.
Concept
Every recursive split eventually bottoms out at a piece with just one element. For a single element, there is only one possible subarray: the element by itself.
So the base case needs no comparison at all — the best subarray sum for a single element is simply that element's value. This is what stops the splitting.
Step zero
Discussion prompt
Recursion in action: splitting the left half again — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Solve the left-left piece, positions 0 through 2
Answer:
Worked example
The left half found above, positions 0 through 4, is itself split in half and solved the same recursive way.
\[ A[0..4] = [-2,1,-3,4,-1] \ \Rightarrow\ \text{split into } A[0..2] \text{ and } A[3..4] \]
Solve the left-left piece, positions 0 through 2
Why: Checking every subarray of -2, 1, -3 by hand, the best is a single element: position 1 by itself, worth 1.
\[ \max\{-2,\ -1,\ -4,\ 1,\ -2,\ -3\} = 1 \]
Solve the right-left piece, positions 3 through 4
Why: Checking every subarray of 4, -1, the best is again a single element: position 3 by itself, worth 4.
\[ \max\{4,\ 3,\ -1\} = 4 \]
Find this level's crossing case and combine
Why: The best suffix ending at position 2 is -2 (positions 1 through 2), and the best prefix starting at position 3 is 4 (position 3 alone), giving a crossing total of 2 — worse than the right-left piece's 4.
\[ \text{crossing} = -2 + 4 = 2; \quad \max(1,\ 4,\ 2) = 4 \]
Verify this matches the left-half answer used earlier
Why: This recursive call reports 4 as the best subarray sum for positions 0 through 4 — exactly the left-half value that was plugged into the crossing computation on the previous slide.
\[ \text{left half } A[0..4] \text{ best} = 4\ \checkmark \]
Worked example
Put all three cases together for the top-level split of the running example array.
Read off the left-half best
Why: Recursively solving positions 0 through 4 (shown on the previous slide) gives a best sum of 4.
\[ \text{left half best} = 4 \]
Read off the right-half best
Why: Checking every subarray of positions 5 through 8 (2, 1, -5, 4) by hand, the best is positions 5 through 6, worth 3.
\[ \text{right half best} = 3 \]
Read off the crossing best
Why: This was computed two slides ago: the best suffix (3) plus the best prefix (3) gives a crossing total of 6.
\[ \text{crossing best} = 6 \]
Verify the maximum of the three cases matches brute force
Why: Comparing 4, 3, and 6, the crossing case wins with 6, for positions 3 through 6. This is exactly the sum brute force found on the same array.
\[ \max(4, 3, 6) = 6\ \checkmark \]
Picture it
Animation
Shows: The divide-and-conquer route — a rendered Manim animation.
Rendered with Manim.
Takeaway: The crossing case is the one people forget, and it is the interesting one.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student assumes the best subarray must live entirely inside one half or the other, and only compares those two.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Taking the larger of 4 and 3 gives 4 as the reported answer.
Always compute the crossing case too, using the two-scan method, and compare all three candidates together.
Why: Taking the larger of 4 and 3 gives 4 as the reported answer. But the true best subarray, positions 3 through 6, straddles the boundary between the halves and was never considered.
Trap
A student assumes the best subarray must live entirely inside one half or the other, and only compares those two.
\[ \text{left half best} = 4, \quad \text{right half best} = 3 \]
Report the larger of just the two halves
Why: Taking the larger of 4 and 3 gives 4 as the reported answer. But the true best subarray, positions 3 through 6, straddles the boundary between the halves and was never considered.
\[ \max(4, 3) = 4 \quad (\text{wrong — misses the true best of } 6) \]
Always compute the crossing case too, using the two-scan method, and compare all three candidates together.
\[ \text{left half best} = 4, \quad \text{right half best} = 3, \quad \text{crossing best} = 6 \]
Compare all three cases, not just two
Why: Including the crossing case reveals the true winner: 6, at positions 3 through 6, which neither half alone could ever produce.
\[ \max(4, 3, 6) = 6\ \checkmark \]
Break the constraint
Discussion prompt
The rule this trap just fixed:
Including the crossing case reveals the true winner: 6, at positions 3 through 6, which neither half alone could ever produce.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
Taking the larger of 4 and 3 gives 4 as the reported answer. But the true best subarray, positions 3 through 6, straddles the boundary between the halves and was never considered.
Concept
Name the running time for an array of size n as a function, T of n. Solving the left half costs T of n over 2, and solving the right half costs another T of n over 2, since each half has about half the elements.
\[ T(n) = T\!\left(\tfrac{n}{2}\right) + T\!\left(\tfrac{n}{2}\right) + \text{(crossing work)} \]
The crossing work is the two linear scans — one leftward from the midpoint, one rightward — each visiting at most every element once, so together they cost a constant times n.
\[ T(n) = 2\,T\!\left(\tfrac{n}{2}\right) + \Theta(n) \]
Intuition
At the very top level, the crossing work touches roughly n elements. One level down, there are two subproblems, each of half the size, so their crossing work is n over 2 plus n over 2 — n again, total.
Every level of the recursion, no matter how many pieces it has been split into, does about the same total amount of crossing work: proportional to n.
\[ \text{level work} \approx n \quad \text{at every level} \]
Intuition
Each level cuts the piece size in half. Starting from the running example's size of nine, the sizes shrink roughly like nine, then about four to five, then about two, then one — the base case.
The number of times you can cut a size in half before reaching one element is the base-two logarithm of the size. That is where the log n term comes from.
\[ \text{number of levels} \approx \log_2 n \]
Concept
Multiply the work done per level, proportional to n, by the number of levels, proportional to log n.
\[ T(n) = 2\,T\!\left(\tfrac{n}{2}\right) + \Theta(n) \ \Longrightarrow\ T(n) = \Theta(n \log n) \]
This is a genuine improvement over the brute-force quadratic time, though it still requires the recursive splitting and the two extra scans at every level.
Section
Section 4
Intuition
What feels wrong about this?
Brute force with the running-sum trick considers every start and every end:
\[ \Theta(n^{2}) \; \text{subarrays} \]
It is already much better than the three-loop version. It is still quadratic.
_Plain English only. No notation, no algebra. Just say what bothers you._
The feeling: when you slide the start one place to the right, you throw away everything you learned about the previous start and begin again from nothing.
That feeling is the proof. It is not a substitute for the proof — it is the thing the proof writes down.
So the question becomes: what could you carry forward from one position to the next? Answer that and the second loop disappears.
Concept
Instead of splitting the array, walk through it once, left to right, and at every position ask one question: what is the best subarray that ends exactly at this position?
best ending here — The sum of the best possible subarray whose last element is the current position — call this running quantity cur. The best answer overall is the largest value cur ever takes.
Definition probe
Sort into buckets
Every line below is part of the definition of subarray or of best ending here — one or the other, never both. Put each where it belongs.
Intuition
At each new position, the best subarray ending here has only two possible origins: either it extends the best subarray that ended at the previous position by tacking on this one new element, or the previous run was so damaging that it is better to restart fresh, using only this element by itself.
There is no third option — any subarray ending here either includes the position before it or it does not.
Picture it
Animation
Shows: Why restarting is ever correct — a rendered Manim animation.
Rendered with Manim.
Takeaway: That single observation is the whole algorithm.
Fill the middle
Fill in the blanks
From The move behind extend-or-restart, named — finish the line. Write what belongs on the right of the equals sign before you look.
\text\max\big(\, \text{best}(i-1) + A[i], \;\; A[i] \,\big)(i) = ___
Why: Producing the right-hand side unprompted is the difference between recognising this line and being able to use it. You are standing at position i, building a subarray that ends here.
Concept
The move: #12 (Case-split on the last decision).
Ask what the last decision is
Why: You are standing at position i, building a subarray that ends here. The only decision left is whether this element joins the run that ended at i-1, or starts a fresh run of its own.
Write each case using a smaller answer
Why: Extend gives the best-ending-at-i-minus-1 plus this element. Restart gives just this element. Two cases, both in terms of something one step smaller.
\[ \text{best}(i) = \max\big(\, \text{best}(i-1) + A[i], \;\; A[i] \,\big) \]
Base case: nothing left to decide
Why: At position 0 there is no earlier run, so best(0) is A[0].
That recurrence was not discovered by cleverness. It fell out of naming the last decision. You are going to do exactly this, five more times, over the next three lessons.
Notation
Annotate
From The move behind extend-or-restart, named — read this one piece at a time. What is each part doing?
On: \( \text{best}(i) = \max\big(\, \text{best}(i-1) + A[i], \;\; A[i] \,\big) \)
Concept
Every proof of this kind has the same five or six moves in the same order. The order is not something you rediscover each time.
It is on the right. It will stay on the right through the worked examples that follow.
Why this matters: the structure is now handled. You are not spending working memory on what comes next — you are spending all of it on the one hard step.
Step 1 is the whole thing. If you cannot name the last decision, you cannot write the recurrence — and no amount of staring at the array will produce one.
Picture it
Animation
Shows: The one decision Kadane makes — a rendered Manim animation.
Rendered with Manim.
Takeaway: Every element poses exactly this question, and answering it is O(1).
Intuition
What move should we make next?
Two candidate definitions for what the table entry means:
\[ (a) \;\; \text{best}(i) = \text{best subarray anywhere in } A[0 \ldots i] \]
\[ (b) \;\; \text{best}(i) = \text{best subarray ending exactly at } i \]
Only one of these lets you write a recurrence.
Pick one and say why the other one fails. This is a question about the definition, not about the code.
_Look at your toolkit. Say a move number out loud before this slide advances._ A wrong guess is useful. A silent guess is not.
Concept
Call the running best-ending-here value cur, and the best value seen at any point so far best. At each new position, update cur by comparing the extend option against the restart option.
\[ \text{cur} = \max\big(\,A[i],\ \ \text{cur}_{\text{previous}} + A[i]\,\big) \]
Then update the overall best by comparing it against the freshly updated cur.
\[ \text{best} = \max(\text{best},\ \text{cur}) \]
Concept
One pass, two variables, and a single decision repeated at every position: extend the run you are on, or abandon it and start fresh here.
KADANE(A)
best = A[1]
here = A[1]
for i = 2 to A.length
here = max(A[i], here + A[i])
best = max(best, here)
return bestLine 5 is the whole algorithm. If the run so far is dragging you below the current element on its own, drop it — a negative prefix can never help what comes after it. Line 6 just remembers the best answer seen.
Notation
Every line of KADANE says one thing. Read the line, then read what it does — not the other way round.
Annotate
here means: the best subarray that ENDS exactly at position i. Not the best anywhere — the best ending here.best means: the best subarray seen anywhere so far. Keeping these two separate is the thing people get wrong.here has gone negative, because a negative prefix only ever subtracts from what follows.best never shrinks.Invariant
After processing position i, here is the best sum ending exactly at i, and best is the best sum ending anywhere at or before i. Check both at every step.
Step through it
At each position, decide extend-or-restart yourself before looking at the answer.
Picture it
Animation
Shows: KADANE executing: the current line of pseudocode is highlighted while the data it touches changes.
Rendered with Manim.
Takeaway: At each position, extend the run or restart from here — whichever is larger. A negative running total is never worth carrying forward.
Concept
Divide and conquer needed to look ahead at both halves before it could combine an answer. Kadane's rule only ever needs the single number cur from the position just before — nothing further back, and nothing from later in the array.
Because each step only depends on the step immediately before it, a single left-to-right pass is enough. No splitting, no recursion, no crossing case to worry about.
Picture it
Animation
Shows: Kadane's invariant — a rendered Manim animation.
Rendered with Manim.
Takeaway: Two quantities, not one — conflating them is the usual bug.
Ranking
Put in order
Put the moves of Full Kadane trace on the running example into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. With no previous position to extend from, cur must equal the first element itself, and best starts out equal to cur.
Worked example
Walk the extend-or-restart rule across the entire running example array, one position at a time.
\[ A = [-2,\ 1,\ -3,\ 4,\ -1,\ 2,\ 1,\ -5,\ 4] \]
Initialize at position 0
Why: With no previous position to extend from, cur must equal the first element itself, and best starts out equal to cur.
| i | A[i] | cur | best |
|---|---|---|---|
| 0 | -2 | -2 | -2 |
Apply extend-or-restart at every remaining position
Why: At each step, compare extending the previous cur by the new element against restarting at the new element alone, and take the larger; then update best if this new cur beats it.
| i | A[i] | cur | best |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | -3 | -2 | 1 |
| 3 | 4 | 4 | 4 |
| 4 | -1 | 3 | 4 |
| 5 | 2 | 5 | 5 |
| 6 | 1 | 6 | 6 |
| 7 | -5 | 1 | 6 |
| 8 | 4 | 5 | 6 |
Verify the final best matches brute force and divide-and-conquer
Why: The trace ends with best equal to 6, first reached at position 6, matching both earlier methods exactly.
\[ \text{best} = 6\ \checkmark \]
Picture it
Animation
Shows: Each line of the worked example "Full Kadane trace on the running example", appearing one at a time.
The same working the example does, in the order a tutor would write it.
Takeaway: The trace ends with best equal to 6, first reached at position 6, matching both earlier methods exactly.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student writes cur as the larger of zero and the extended value, thinking of zero as a safe floor whenever the running total goes negative.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Since every element is negative, cur plus a negative element is always negative, so the comparison against zero always picks zero.
Never compare against zero. Only compare the element alone against the extended running total, exactly as the recurrence states.
Why: Since every element is negative, cur plus a negative element is always negative, so the comparison against zero always picks zero. Every single position ends up with cur equal to zero.
Trap
A student writes cur as the larger of zero and the extended value, thinking of zero as a safe floor whenever the running total goes negative.
\[ \text{cur} = \max\big(0,\ \ \text{cur}_{\text{previous}} + A[i]\big) \]
Run this rule on the all-negative array C
Why: Since every element is negative, cur plus a negative element is always negative, so the comparison against zero always picks zero. Every single position ends up with cur equal to zero.
| i | A[i] | cur (reset rule) |
|---|---|---|
| 0 | -8 | 0 |
| 1 | -3 | 0 |
| 2 | -6 | 0 |
| 3 | -2 | 0 |
| 4 | -5 | 0 |
| 5 | -4 | 0 |
See the wrong final answer
Why: The best value never rises above zero, so this rule reports zero as the maximum subarray sum. But zero is not achievable by any non-empty subarray of an all-negative array — the rule has silently invented an empty subarray.
\[ \text{reported best} = 0 \quad (\text{wrong}) \]
Never compare against zero. Only compare the element alone against the extended running total, exactly as the recurrence states.
\[ \text{cur} = \max\big(A[i],\ \ \text{cur}_{\text{previous}} + A[i]\big) \]
Run the correct rule on the same array
Why: Without the zero floor, restarting still means starting fresh at the current negative element, not jumping to zero — so cur tracks real, achievable subarray sums at every position.
| i | A[i] | cur (correct rule) |
|---|---|---|
| 0 | -8 | -8 |
| 1 | -3 | -3 |
| 2 | -6 | -6 |
| 3 | -2 | -2 |
| 4 | -5 | -5 |
| 5 | -4 | -4 |
Verify the correct answer is the least negative element
Why: The largest value in the cur column is -2, at position 3, matching the brute-force answer on this same array exactly.
\[ \text{best} = -2\ \checkmark \]
Invariant
Step through it
Step through Trap: resetting the running sum to zero one row at a time. One of these columns never changes — find it, and say why it cannot.
Estimation
Predict first
Confirm the correct extend-or-restart rule end to end on the all-negative array, tracking best alongside cur.
Commit before you compute: what does Kadane on the all-negative array, in full come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Verify against the brute-force trace from earlier
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Both methods report a best sum of -2, achieved by the single element at position 3 — full agreement, and the correct handling of an entirely negative input.
Worked example
Confirm the correct extend-or-restart rule end to end on the all-negative array, tracking best alongside cur.
\[ C = [-8,\ -3,\ -6,\ -2,\ -5,\ -4] \]
Apply the correct rule at every position
Why: At each step, restarting always beats extending, since adding another negative number only makes the running total more negative than the fresh element alone.
| i | A[i] | cur | best |
|---|---|---|---|
| 0 | -8 | -8 | -8 |
| 1 | -3 | -3 | -3 |
| 2 | -6 | -6 | -3 |
| 3 | -2 | -2 | -2 |
| 4 | -5 | -5 | -2 |
| 5 | -4 | -4 | -2 |
Verify against the brute-force trace from earlier
Why: Both methods report a best sum of -2, achieved by the single element at position 3 — full agreement, and the correct handling of an entirely negative input.
\[ \text{best} = -2\ \checkmark \]
Concept
The algorithm makes exactly one pass over the array, doing a constant amount of work — one comparison and one update — at each position.
\[ \Theta(n) \text{ time}, \quad \Theta(1) \text{ extra space} \]
No recursion, no extra arrays, no splitting. This is the fastest of the three approaches, and it is also the simplest to implement correctly.
Picture it
Animation
Shows: Kadane's scan, one element at a time — a rendered Manim animation.
Rendered with Manim.
Takeaway: One pass, one running total, one best-so-far.
Step zero
Discussion prompt
Reporting the start and end positions correctly — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Track a start-of-current-run marker alongside cur
Answer:
Worked example
Tracking the best sum is not enough — a full solution must also report which positions achieved it, without an off-by-one slip.
Track a start-of-current-run marker alongside cur
Why: Whenever cur restarts (the fresh element beats extending), move this marker to the current position. Whenever cur extends, leave the marker where it was.
| i | A[i] | cur | start of current run |
|---|---|---|---|
| 0 | -2 | -2 | 0 |
| 1 | 1 | 1 | 1 |
| 2 | -3 | -2 | 1 |
| 3 | 4 | 4 | 3 |
| 4 | -1 | 3 | 3 |
| 5 | 2 | 5 | 3 |
| 6 | 1 | 6 | 3 |
| 7 | -5 | 1 | 3 |
| 8 | 4 | 5 | 3 |
Only update the reported best-start and best-end when best actually improves
Why: If the reported end were updated on every single step regardless of whether best improved, it would drift forward past the true winning position — exactly the off-by-one trap seen earlier.
| i where best improved | best | reported start | reported end |
|---|---|---|---|
| 0 | -2 | 0 | 0 |
| 1 | 1 | 1 | 1 |
| 3 | 4 | 3 | 3 |
| 5 | 5 | 3 | 5 |
| 6 | 6 | 3 | 6 |
Verify the final reported indices re-sum to the reported best
Why: The last row shows reported start 3, reported end 6, best 6. Re-adding A[3] through A[6] gives 4 minus 1 plus 2 plus 1, which is 6 — the indices and the sum agree exactly.
\[ A[3]+A[4]+A[5]+A[6] = 4-1+2+1 = 6\ \checkmark \]
Pattern
Step through it
Step through Reporting the start and end positions correctly one row at a time. What is driving the change, and what would the row after the last one be?
Section
Section 5
Concept
By convention, the maximum sum subarray problem always requires picking at least one element. An empty selection is never a valid answer, even though its sum is trivially zero.
This convention is exactly why an all-negative array's correct answer is its least negative single element, never zero. Any method that quietly allows an empty subarray — like resetting a running sum to zero — will get this case wrong.
Picture it
Animation
Shows: The two-dimensional version — a rendered Manim animation.
Rendered with Manim.
Takeaway: Reduce the new problem to the one you have already solved.
Estimation
Predict first
As a final cross-check, apply the straightforward brute-force scan to every starting position of the full nine-element array A.
Commit before you compute: what does Brute force confirms the answer on the full running example come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Verify the overall winner matches both other methods
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The largest value in the table is 6, from start position 3, matching the divide-and-conquer combine and the Kadane trace exactly, both of which also landed on positions 3 through 6.
Worked example
As a final cross-check, apply the straightforward brute-force scan to every starting position of the full nine-element array A.
Compute the best sum for each starting position
Why: For each start, extend the end marker across the rest of the array and keep the largest running sum reached.
| start | best sum from this start |
|---|---|
| 0 | 2 |
| 1 | 4 |
| 2 | 3 |
| 3 | 6 |
| 4 | 2 |
| 5 | 3 |
| 6 | 1 |
| 7 | -1 |
| 8 | 4 |
Verify the overall winner matches both other methods
Why: The largest value in the table is 6, from start position 3, matching the divide-and-conquer combine and the Kadane trace exactly, both of which also landed on positions 3 through 6.
\[ \max(2,4,3,6,2,3,1,-1,4) = 6\ \checkmark \]
Picture it
Animation
Shows: Each line of the worked example "Brute force confirms the answer on the full running example", appearing one at a time.
The same working the example does, in the order a tutor would write it.
Takeaway: The largest value in the table is 6, from start position 3, matching the divide-and-conquer combine and the Kadane trace exactly, both of which also landed on positions 3 through 6.
Intuition
Brute force, divide-and-conquer, and Kadane's algorithm all found the same subarray, positions 3 through 6, with the same sum.
| method | best sum found | positions |
|---|---|---|
| brute force | 6 | 3 to 6 |
| divide and conquer | 6 | 3 to 6 |
| Kadane's algorithm | 6 | 3 to 6 |
That agreement is not a coincidence — it is a correctness check. Any time you write a fast algorithm, checking it against a slow, obviously-correct brute force on the same input is exactly how you catch bugs.
Comparison
Comparison matrix
From All three methods agree: refill the positions column from what you know. The rest of the table is as it appeared.
| method | best sum found | positions |
|---|---|---|
| brute force | 6 | 3 to 6 |
| divide and conquer | 6 | 3 to 6 |
| Kadane's algorithm | 6 | 3 to 6 |
Intuition
What move should we make next?
Same answers, three running times:
\[ \Theta(n^{2}) \qquad \Theta(n \log n) \qquad \Theta(n) \]
For each of the three, name the move or moves that produced its running time. You have the tools to account for all three.
_Look at your toolkit. Say a move number out loud before this slide advances._ A wrong guess is useful. A silent guess is not.
Explain it
Discussion prompt
Explain Decision point: three algorithms, one problem to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
For each of the three, name the move or moves that produced its running time. You have the tools to account for all three.
Concept
Same problem, same answer, three very different running times.
| approach | time complexity | core idea |
|---|---|---|
| brute force | quadratic | try every start and end, running sum |
| divide and conquer | n log n | split, solve each half, add the crossing case |
| Kadane's algorithm | linear | one pass, extend or restart at each step |
Comparison
Comparison matrix
From Comparing the three approaches: refill the time complexity column from what you know. The rest of the table is as it appeared.
| approach | time complexity | core idea |
|---|---|---|
| brute force | quadratic | try every start and end, running sum |
| divide and conquer | n log n | split, solve each half, add the crossing case |
| Kadane's algorithm | linear | one pass, extend or restart at each step |
Intuition
Brute force is worth writing first anyway — it is nearly impossible to get wrong, and it is the correctness baseline everything else gets checked against.
Divide and conquer is worth understanding because the same left-half, right-half, crossing-case pattern reappears in many other problems throughout this course. In production code for this specific problem, though, Kadane's linear scan wins outright — it is both the fastest and the simplest to implement.
Analogy
Discussion prompt
Explain When would you actually pick each one? by analogy to something with no CS3000 Algorithms in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Brute force is worth writing first anyway — it is nearly impossible to get wrong, and it is the correctness baseline everything else gets checked against.
Pattern
1. Pin down what counts as a valid answer
Why: Confirm the answer must be a contiguous, non-empty run — not any subset of elements, and not an empty selection worth zero.
2. Start with brute force for a correctness baseline
Why: Try every candidate directly, using a running sum instead of resumming from scratch. It is slow but unmistakably correct.
3. Look for optimal substructure to speed it up
Why: Split the problem and enumerate every place the answer could hide — here, entirely left, entirely right, or crossing the split. Never skip the crossing case.
4. Look for a single left-to-right recurrence
Why: If each answer only depends on the one immediately before it, a single pass replaces the whole recursive structure — extend or restart, no splitting needed.
5. Stress-test the edge cases before trusting the code
Why: Check an all-negative input, and re-sum the reported start and end positions directly to catch any off-by-one drift.
Real world
Discussion prompt
Outside this lesson: where does Maximum Sum Subarray actually turn up? Name one concrete situation — a job, a piece of software someone ships, a decision somebody has to make — and say which part of The unifying recipe is doing the work in it.
Hint: Vague is the failure mode here. "Engineering" is not a situation; "deciding whether this build is fast enough to ship" is.
Answer:
The maximum sum subarray problem worked three ways: brute force, divide-and-conquer (with the crossing subarray case), and Kadane's linear scan. Targets confusing a subarray with a subsequence, forgetting the crossing case in divide-and-conquer, resetting Kadane's running sum to zero on an all-negative array, and off-by-one errors when reporting the start/end indices.
Picture it
Animation
Shows: Reporting WHERE, not just how much — a rendered Manim animation.
Rendered with Manim.
Takeaway: Two extra variables turn a value into an answer.
Ranking
Put in order
Put the moves of Applying the recipe to a brand-new array into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. At each step, compare extending the previous cur by the new element against restarting fresh at the new element alone.
Worked example
Try the recipe on an array we have not seen before, to confirm it transfers.
\[ D = [\,3,\ -2,\ 5,\ -1,\ 6,\ -3\,] \]
Run Kadane's extend-or-restart rule position by position
Why: At each step, compare extending the previous cur by the new element against restarting fresh at the new element alone.
| i | D[i] | cur | best |
|---|---|---|---|
| 0 | 3 | 3 | 3 |
| 1 | -2 | 1 | 3 |
| 2 | 5 | 6 | 6 |
| 3 | -1 | 5 | 6 |
| 4 | 6 | 11 | 11 |
| 5 | -3 | 8 | 11 |
Read off the winning positions
Why: Best first reaches 11 at position 4, and tracking the restart marker shows the run has been extending continuously since position 0 — so the winning subarray spans positions 0 through 4.
\[ \text{positions 0 through 4}: 3,-2,5,-1,6 \]
Verify by re-adding those five elements directly
Why: 3 plus negative 2 plus 5 plus negative 1 plus 6 equals 11, exactly matching the reported best — the algorithm and a direct check agree.
\[ 3-2+5-1+6 = 11\ \checkmark \]
Picture it
Animation
Shows: Each line of the worked example "Applying the recipe to a brand-new array", appearing one at a time.
The same working the example does, in the order a tutor would write it.
Takeaway: 3 plus negative 2 plus 5 plus negative 1 plus 6 equals 11, exactly matching the reported best — the algorithm and a direct check agree.
Elimination
Eliminate the wrong options
Which of these is a valid subarray of A?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: A subarray must be contiguous, with no skipped positions in between the start and the end. Positions 3 through 6 include every index in that range, so it qualifies. The others skip indices (making them subsequences, not subarrays) or select nothing at all, which is excluded since the problem requires a non-empty answer.
Check
Recall the running example array and its structure.
\[ A = [-2,\ 1,\ -3,\ 4,\ -1,\ 2,\ 1,\ -5,\ 4] \]
Check your understanding
Which of these is a valid subarray of A?
Answer: A
Why: A subarray must be contiguous, with no skipped positions in between the start and the end. Positions 3 through 6 include every index in that range, so it qualifies. The others skip indices (making them subsequences, not subarrays) or select nothing at all, which is excluded since the problem requires a non-empty answer.
Prediction
Predict first
In divide and conquer, what goes wrong if you only compare the best subarray of the left half to the best subarray of the right half?
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: You might miss the true best subarray if it straddles the midpoint
Why: On the running example, the true best subarray spans positions 3 through 6 with a sum of 6, straddling the boundary between the halves. Comparing only the whole-half maximums (4 and 3) would report 4 and completely miss the crossing subarray's larger sum.
Check
Think back to the divide-and-conquer approach and the trap about skipping a case.
Check your understanding
In divide and conquer, what goes wrong if you only compare the best subarray of the left half to the best subarray of the right half?
Answer: A
Why: On the running example, the true best subarray spans positions 3 through 6 with a sum of 6, straddling the boundary between the halves. Comparing only the whole-half maximums (4 and 3) would report 4 and completely miss the crossing subarray's larger sum.
Commit first
Predict first
Running the correct Kadane recurrence (no comparison against zero) on array C, what is the maximum subarray sum?
Commit to an answer, then rate it — certain, fairly sure, or guessing — and write the rating down before you turn the page.
Correct: -2, the least negative single element
Why: Since every element is negative, extending never helps, so the correct rule restarts at every position and cur equals that position's own value. The largest of those values is -2, at position 3, matching the brute-force trace on the same array exactly.
The rating matters as much as the answer: confident-and-wrong is the combination that survives revision, because nothing about it feels like it needs revisiting.
Check
Recall the all-negative example array and the correct, non-resetting extend-or-restart rule.
\[ C = [-8,\ -3,\ -6,\ -2,\ -5,\ -4] \]
Check your understanding
Running the correct Kadane recurrence (no comparison against zero) on array C, what is the maximum subarray sum?
Answer: A
Why: Since every element is negative, extending never helps, so the correct rule restarts at every position and cur equals that position's own value. The largest of those values is -2, at position 3, matching the brute-force trace on the same array exactly.
Prediction
Predict first
Which implementation mistake would cause the reported end position to be one step past the true answer?
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: Updating the reported end position every iteration, instead of only when best actually improves
Why: In the trace, cur kept extending through positions 7 and 8 even though best had already stopped improving after position 6. Updating the reported end on every iteration, rather than only inside the branch where best improves, drags the reported end forward past the true winning position.
Check
A student's Kadane implementation reports the winning subarray as starting at position 3 and ending at position 7, but the correct answer ends at position 6.
Check your understanding
Which implementation mistake would cause the reported end position to be one step past the true answer?
Answer: A
Why: In the trace, cur kept extending through positions 7 and 8 even though best had already stopped improving after position 6. Updating the reported end on every iteration, rather than only inside the branch where best improves, drags the reported end forward past the true winning position.
Elimination
Eliminate the wrong options
Ranking the three approaches from slowest to fastest for large n, which order is correct?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: Brute force is quadratic, divide and conquer is n log n, and Kadane's algorithm is linear. For large n, quadratic grows fastest, n log n grows slower than that, and linear grows slowest of all — so that is the slowest-to-fastest order.
Check
Think about how each approach's running time grows as the array size n gets very large.
Check your understanding
Ranking the three approaches from slowest to fastest for large n, which order is correct?
Answer: A
Why: Brute force is quadratic, divide and conquer is n log n, and Kadane's algorithm is linear. For large n, quadratic grows fastest, n log n grows slower than that, and linear grows slowest of all — so that is the slowest-to-fastest order.
Concept
Moves added today:
Moves you reused today:
Move #12 will run the next three lessons. Every dynamic programming recurrence you write from here is this move applied to a different last decision.
Full toolkit so far: #1 through #12.
Next session opens with you naming every one of these from memory, before any new material.
Counterexample
Discussion prompt
Move #12 will run the next three lessons. Every dynamic programming recurrence you write from here is this move applied to a different last decision.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Next session opens with you naming every one of these from memory, before any new material.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — The Problem · Brute Force · Divide and Conquer · Kadane's Algorithm · Correctness & the Big Picture. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
You solved one problem three ways and confirmed all three agree.
| Technique | The one move |
|---|---|
| Brute force | running sum instead of resumming |
| Divide and conquer | left, right, AND crossing |
| Kadane's algorithm | extend or restart, no reset to zero |
| Reporting indices | re-sum the reported range to check it |
Want this taught 1-on-1? Alexander tutors CS3000 Algorithms — $55/session, free consultation.