Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P0MediumBuilt on Linear DP~3 min speedrun

Kadane's Algorithm (Maximum Subarray)

Built on
Linear DPThe state is the best sum of a subarray that ends at i, and each step either extends it or starts fresh at i.
Grows into
Maximum Product SubarrayCircular KadaneMax Absolute Subarray SumTurbulent SubarrayKadane with One Deletion
The problem

Maximum Subarray (LC 53)(opens leetcode.com)

You're given an integer array nums. A subarray is any non-empty run of elements sitting next to each other in the array, with no skipping.

Scan every possible subarray and return the largest sum any of them adds up to.

Example 1
Input:nums = [-2,1,-3,4,-1,2,1,-5,4]
Output:6
The subarray [4,-1,2,1] has the largest sum 6.

Kadane's Algorithm

dp[i] = best sum ending at i ➔ extend the run or start fresh at x ➔ keep the best dp[i] seen
Spot it when: You need the best sum of one unbroken run of an array, where the run may start and end anywhere and the numbers may be negative.
Kadane's Algorithm
Python
1def max_subarray(nums: list[int]) -> int:
2 """Maximum Subarray: the best sum of one unbroken run, found in a single pass.
3 Time: O(N) | Space: O(1)
4 """
5 # curr: the best sum of a subarray that ends at the current number; best: the largest curr seen so far
6 curr = best = nums[0]
7 for x in nums[1:]:
8 # 1. Extend Or Start Fresh:
9 # A negative curr can only lower x, so the best subarray ending at x is x alone when x > curr + x.
10 curr = max(x, curr + x)
11 
12 # 2. Track The Best:
13 # Every subarray ends at some index, so the largest curr seen at any index is the answer.
14 best = max(best, curr)
15 
16 return best
Invariant: After each x, curr is the best sum of a subarray that ends at x, and best is the largest curr seen so far.

A subarray that ends at x either extends the best subarray that ends just before x, or starts at x alone, so curr = max(x, curr + x): a negative curr can only lower x, so it is dropped. Every subarray ends at some index, so the largest curr seen at any index, kept in best, is the answer. The trap is the start value: set curr = best = nums[0], not 0, because the subarray must hold at least one number, and an array of only negative numbers has a negative answer.

See it run

Kadane's Algorithm, step by step

curr -2 · best -2

curr 1 · best 1

curr -2 · best 1

curr 4 · best 4

curr 3 · best 4

curr 5 · best 5

curr 6 · best 6

curr 1 · best 6

curr 5 · best 6

curr 5 · best 6

The tag x marks the number being read. It is always the last number of the current run.

  • run a number in the current run (the run that ends at x)
  • best a number in the best run found so far
  • both a number in the current run and in the best run

Start: curr = best = nums[0] = -2. The only subarray that ends at index 0 is -2 itself.

Restart: -2 + 1 = -1 is below 1, so the old run is dropped and a new run starts at 1.

Extend: 1 + -3 = -2, which is at least -3, so the run keeps growing.

Restart: -2 + 4 = 2 is below 4, so the old run is dropped and a new run starts at 4.

Extend: 4 + -1 = 3, which is at least -1, so the run keeps growing.

Extend: 3 + 2 = 5, which is at least 2, so the run keeps growing.

Extend: 5 + 1 = 6, which is at least 1, so the run keeps growing.

Extend: 6 + -5 = 1, which is at least -5, so the run keeps growing.

Extend: 1 + 4 = 5, which is at least 4, so the run keeps growing.

Done: best = 6, from the run [4, -1, 2, 1] at indexes 3 to 6.

0-2x
11
2-3
34
4-1
52
61
7-5
84
1 / 10
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Starting curr and best at 0

    Mistake
    curr = best = 0
    Fix
    curr = best = nums[0]

    What happens: Maximum Subarray (LC 53) must take at least one number. Starting best at 0 counts the empty subarray as an answer. When every number is negative, best stays 0. Start from nums[0] and loop over the rest.

    Example: Maximum Subarray (LC 53): nums = [-3, -1, -2]
    Mistake: returns 0Fix: returns -1
  2. 2

    Gotcha 2: Returning curr instead of best

    Mistake
    return curr
    Fix
    return best

    What happens: curr is the best sum of a subarray that ends at the last number. The best subarray can end earlier. After the best run, a negative number lowers curr, but best keeps the peak. Returning curr reports the run at the end of the array, not the best run.

    Example: Maximum Subarray (LC 53): nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    Mistake: returns 5Fix: returns 6
  3. 3

    Gotcha 3: Comparing curr + x with 0 instead of with x

    Mistake
    curr = max(0, curr + x)
    Fix
    curr = max(x, curr + x)

    What happens: Starting fresh means the new subarray holds x, so the choice is x or curr + x. Choosing 0 starts an empty subarray. When every number is negative, curr becomes 0 and best can never go below 0.

    Example: Maximum Subarray (LC 53): nums = [-2, -1]
    Mistake: returns 0Fix: returns -1
  4. 4

    Gotcha 4: Returning only the sum when the problem asks for the subarray

    Mistake
    if x > curr + x:
        curr = x
    Fix
    if x > curr + x:
        curr = x
        start = i

    What happens: Some follow-ups ask for the run itself, not its sum. The run starts again at i each time curr restarts at x. Tracking start only once, at index 0, leaves start at 0 for the whole loop. The saved run then begins at the first number, so it includes numbers that lower the sum.

    Example: Maximum Subarray (LC 53), asked for the run itself: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    Mistake: returns [-2, 1, -3, 4, -1, 2, 1]Fix: returns [4, -1, 2, 1]
Not this one if…
  • The target is an exact sum, not a best sum, and the numbers can be negative, so dropping curr never finds the runs, as in Subarray Sum Equals K (LC 560): count the earlier prefix sums in a map. See Prefix Sum Map
  • The chosen numbers are separate and no two may touch, instead of one unbroken run, as in House Robber (LC 198). See House Robber

Deep Dive

Why curr = max(x, curr + x) is correct

A subarray is one unbroken run of the array. If every number is positive, the answer is the whole array, because each extra number raises the sum. Negative numbers make the problem hard, because the run must decide which numbers to keep.

Here dp[i] is the best sum of a subarray that ends exactly at index i. It is not the best sum in the first i numbers.

Look at a subarray that ends at x. It has only two forms. It is x alone, or it is a subarray that ends just before x, followed by x.

For the second form, the best choice is the best subarray that ends just before x. That value is curr. So the best sum ending at x is max(x, curr + x).

When curr is negative, curr + x is smaller than x, so the old run is dropped and a new run starts at x.

Why best is the answer

Every subarray ends at some index. The best subarray overall ends at one of them, and at that index curr already equals its sum.

So the largest curr over all indices is the answer. The variable best keeps that value.

This is why curr and best are two variables. curr can go down after a negative number. best never goes down.

Trace for nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]. The rows marked restart are the steps where curr drops the old run and starts again at x:

xcurr + xcurr afterbest afterStep
-2start-2-2first number
1-111restart
-3-2-21extend
4244restart
-1334extend
2555extend
1666extend
-5116extend
4556extend

The answer is 6, from the run [4, -1, 2, 1].

Two steps restart the run. At 1, curr becomes 1 and not -2 + 1 = -1, because the old run was negative. At 4, curr becomes 4 and not -2 + 4 = 2, for the same reason. These restarts are where Kadane's rule does its work.

What this card adds to Linear DP

Linear DP keeps dp[i - 1] and dp[i - 2]. Here the formula needs only dp[i - 1], which is curr. The second variable best is not part of the recurrence. It records the maximum over all states.

This is a general pattern. When the answer is the best of all dp[i] and not the last dp[i], keep a running maximum next to the rolling value.

The start is curr = best = nums[0]. The subarray must hold at least one number, so an empty run at 0 is not allowed. An array of only negative numbers has a negative answer. For [-3, -1, -2], the answer is -1.

The cost and the follow-ups

There are N states, and each does two max calls. The time is O(N) and the space is O(1).

The brute force checks every start and end, which is N * (N + 1) / 2 subarrays. For the nine numbers above, that is 45. The loop makes 8 passes.

Adding up each subarray from scratch costs O(N^3) in total. Keeping a running sum while the end moves right lowers it to O(N^2). Kadane's algorithm removes the loop over the start, so it is O(N).

To return the run and not only its sum, record start = i each time curr restarts at x. Record best_start and best_end each time best increases.

To find the best rectangle in a matrix of numbers, pick two rows r1 and r2. Add the numbers of each column from r1 to r2 into one array. The best run in that array is the best rectangle between those two rows, so run Kadane on it. There are O(rows^2) row pairs and each Kadane pass costs O(cols), so the total is O(rows^2 * cols).

For a circular array, as in Maximum Sum Circular Subarray (LC 918), the answer is the larger of two values. One is the normal Kadane result. The other is the total sum minus the smallest subarray sum. If every number is negative, return the normal result.

60-Second Pitch

"I define curr as the best sum of a subarray that ends at the current number. That subarray is either the number alone, or the earlier best run plus the number. So curr = max(x, curr + x). Every subarray ends somewhere, so the largest curr over all positions is the answer. I keep it in best. I start both from nums[0], because the subarray must contain at least one number. One pass gives O(N) time and O(1) space."

Common Questions

Why is it correct to drop the earlier run when it is negative?

A negative curr makes every subarray that includes it smaller than the same subarray without it. So curr + x is less than x. Starting at x is always at least as good. This is why curr = max(x, curr + x) is enough.

What does Kadane's algorithm return when all numbers are negative?

The largest single number. The start curr = best = nums[0] and the comparison with x allow this. For [-3, -1, -2], the answer is -1. A version that starts at 0 returns 0, which is wrong, because the subarray must have at least one number.

How do you return the subarray and not only the sum?

Add start. Set start = i when the run restarts at x. When best increases, save best_start = start and best_end = i. For the LeetCode example, the saved run is [4, -1, 2, 1].

How does this change for Maximum Product Subarray (LC 152)?

Keep two values, the largest and the smallest product that end at i. A negative x swaps their roles, because a negative number times the smallest product can become the largest. One value is not enough.

How do you solve Maximum Sum Circular Subarray (LC 918)?

The best run either stays inside the array, or wraps around. For a run that stays inside, use Kadane. For a run that wraps, take the total sum minus the smallest subarray sum, which is Kadane on the negated numbers. Return the larger value. If all numbers are negative, return the plain result, because the wrapping case would be an empty run.

SWE Reasoning

Spot it

  • The question asks for the best sum of one unbroken run of an array.
  • The run can start and end anywhere.
  • The numbers can be negative, so the whole array is not always the best run.
  • The brute force tries every start and end in O(N^2), and a one-pass answer is wanted.
  • Every subarray can be described by where it ends. This suggests the state "best sum ending at i".

The invariant, step by step

  1. Before the loop, curr and best are both nums[0]. The only subarray that ends at index 0 is nums[0].
  2. For the next number x, curr is the best sum of a subarray that ends just before x.
  3. curr = max(x, curr + x) picks the better of two forms: extend the run, or start fresh at x.
  4. Now curr is the best sum of a subarray that ends at x.
  5. best = max(best, curr) keeps the largest curr seen so far.
  6. After the last number, best covers every end index, so it is the answer.

Traps

  • Starting at 0. curr = best = 0 allows an empty subarray. For [-3, -1, -2], the result is 0 instead of -1.
  • Returning curr. curr is the best sum ending at the last number. For the LeetCode example, it is 5 and the answer is 6.
  • Comparing with 0. curr = max(0, curr + x) starts an empty run. For [-2, -1], the result is 0 instead of -1.
  • Start index never moves. To return the run, set start = i on every restart. If start stays at 0, the run begins too early and includes numbers that lower the sum.
  • Wrong problem. A product, or an exact target sum, does not work with this rule. A negative number can turn a small product into a large one, and an exact sum needs a different method.

Practice

Canonical LeetCode:Maximum Subarray (LC 53)Maximum Sum Circular Subarray (LC 918)Maximum Product Subarray (LC 152)Best Time to Buy and Sell Stock (LC 121)
Theory Context•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P0MediumBuilt on Linear DP~3 min speedrun

Kadane's Algorithm (Maximum Subarray)

Built on
Linear DPThe state is the best sum of a subarray that ends at i, and each step either extends it or starts fresh at i.
Grows into
Maximum Product SubarrayCircular KadaneMax Absolute Subarray SumTurbulent SubarrayKadane with One Deletion
The problem

Maximum Subarray (LC 53)(opens leetcode.com)

You're given an integer array nums. A subarray is any non-empty run of elements sitting next to each other in the array, with no skipping.

Scan every possible subarray and return the largest sum any of them adds up to.

Example 1
Input:nums = [-2,1,-3,4,-1,2,1,-5,4]
Output:6
The subarray [4,-1,2,1] has the largest sum 6.

Kadane's Algorithm

dp[i] = best sum ending at i ➔ extend the run or start fresh at x ➔ keep the best dp[i] seen
Spot it when: You need the best sum of one unbroken run of an array, where the run may start and end anywhere and the numbers may be negative.
Kadane's Algorithm
Python
1def max_subarray(nums: list[int]) -> int:
2 """Maximum Subarray: the best sum of one unbroken run, found in a single pass.
3 Time: O(N) | Space: O(1)
4 """
5 # curr: the best sum of a subarray that ends at the current number; best: the largest curr seen so far
6 curr = best = nums[0]
7 for x in nums[1:]:
8 # 1. Extend Or Start Fresh:
9 # A negative curr can only lower x, so the best subarray ending at x is x alone when x > curr + x.
10 curr = max(x, curr + x)
11 
12 # 2. Track The Best:
13 # Every subarray ends at some index, so the largest curr seen at any index is the answer.
14 best = max(best, curr)
15 
16 return best
Invariant: After each x, curr is the best sum of a subarray that ends at x, and best is the largest curr seen so far.

A subarray that ends at x either extends the best subarray that ends just before x, or starts at x alone, so curr = max(x, curr + x): a negative curr can only lower x, so it is dropped. Every subarray ends at some index, so the largest curr seen at any index, kept in best, is the answer. The trap is the start value: set curr = best = nums[0], not 0, because the subarray must hold at least one number, and an array of only negative numbers has a negative answer.

See it run

Kadane's Algorithm, step by step

curr -2 · best -2

curr 1 · best 1

curr -2 · best 1

curr 4 · best 4

curr 3 · best 4

curr 5 · best 5

curr 6 · best 6

curr 1 · best 6

curr 5 · best 6

curr 5 · best 6

The tag x marks the number being read. It is always the last number of the current run.

  • run a number in the current run (the run that ends at x)
  • best a number in the best run found so far
  • both a number in the current run and in the best run

Start: curr = best = nums[0] = -2. The only subarray that ends at index 0 is -2 itself.

Restart: -2 + 1 = -1 is below 1, so the old run is dropped and a new run starts at 1.

Extend: 1 + -3 = -2, which is at least -3, so the run keeps growing.

Restart: -2 + 4 = 2 is below 4, so the old run is dropped and a new run starts at 4.

Extend: 4 + -1 = 3, which is at least -1, so the run keeps growing.

Extend: 3 + 2 = 5, which is at least 2, so the run keeps growing.

Extend: 5 + 1 = 6, which is at least 1, so the run keeps growing.

Extend: 6 + -5 = 1, which is at least -5, so the run keeps growing.

Extend: 1 + 4 = 5, which is at least 4, so the run keeps growing.

Done: best = 6, from the run [4, -1, 2, 1] at indexes 3 to 6.

0-2x
11
2-3
34
4-1
52
61
7-5
84
1 / 10
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Starting curr and best at 0

    Mistake
    curr = best = 0
    Fix
    curr = best = nums[0]

    What happens: Maximum Subarray (LC 53) must take at least one number. Starting best at 0 counts the empty subarray as an answer. When every number is negative, best stays 0. Start from nums[0] and loop over the rest.

    Example: Maximum Subarray (LC 53): nums = [-3, -1, -2]
    Mistake: returns 0Fix: returns -1
  2. 2

    Gotcha 2: Returning curr instead of best

    Mistake
    return curr
    Fix
    return best

    What happens: curr is the best sum of a subarray that ends at the last number. The best subarray can end earlier. After the best run, a negative number lowers curr, but best keeps the peak. Returning curr reports the run at the end of the array, not the best run.

    Example: Maximum Subarray (LC 53): nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    Mistake: returns 5Fix: returns 6
  3. 3

    Gotcha 3: Comparing curr + x with 0 instead of with x

    Mistake
    curr = max(0, curr + x)
    Fix
    curr = max(x, curr + x)

    What happens: Starting fresh means the new subarray holds x, so the choice is x or curr + x. Choosing 0 starts an empty subarray. When every number is negative, curr becomes 0 and best can never go below 0.

    Example: Maximum Subarray (LC 53): nums = [-2, -1]
    Mistake: returns 0Fix: returns -1
  4. 4

    Gotcha 4: Returning only the sum when the problem asks for the subarray

    Mistake
    if x > curr + x:
        curr = x
    Fix
    if x > curr + x:
        curr = x
        start = i

    What happens: Some follow-ups ask for the run itself, not its sum. The run starts again at i each time curr restarts at x. Tracking start only once, at index 0, leaves start at 0 for the whole loop. The saved run then begins at the first number, so it includes numbers that lower the sum.

    Example: Maximum Subarray (LC 53), asked for the run itself: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    Mistake: returns [-2, 1, -3, 4, -1, 2, 1]Fix: returns [4, -1, 2, 1]
Not this one if…
  • The target is an exact sum, not a best sum, and the numbers can be negative, so dropping curr never finds the runs, as in Subarray Sum Equals K (LC 560): count the earlier prefix sums in a map. See Prefix Sum Map
  • The chosen numbers are separate and no two may touch, instead of one unbroken run, as in House Robber (LC 198). See House Robber

Deep Dive

Why curr = max(x, curr + x) is correct

A subarray is one unbroken run of the array. If every number is positive, the answer is the whole array, because each extra number raises the sum. Negative numbers make the problem hard, because the run must decide which numbers to keep.

Here dp[i] is the best sum of a subarray that ends exactly at index i. It is not the best sum in the first i numbers.

Look at a subarray that ends at x. It has only two forms. It is x alone, or it is a subarray that ends just before x, followed by x.

For the second form, the best choice is the best subarray that ends just before x. That value is curr. So the best sum ending at x is max(x, curr + x).

When curr is negative, curr + x is smaller than x, so the old run is dropped and a new run starts at x.

Why best is the answer

Every subarray ends at some index. The best subarray overall ends at one of them, and at that index curr already equals its sum.

So the largest curr over all indices is the answer. The variable best keeps that value.

This is why curr and best are two variables. curr can go down after a negative number. best never goes down.

Trace for nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]. The rows marked restart are the steps where curr drops the old run and starts again at x:

xcurr + xcurr afterbest afterStep
-2start-2-2first number
1-111restart
-3-2-21extend
4244restart
-1334extend
2555extend
1666extend
-5116extend
4556extend

The answer is 6, from the run [4, -1, 2, 1].

Two steps restart the run. At 1, curr becomes 1 and not -2 + 1 = -1, because the old run was negative. At 4, curr becomes 4 and not -2 + 4 = 2, for the same reason. These restarts are where Kadane's rule does its work.

What this card adds to Linear DP

Linear DP keeps dp[i - 1] and dp[i - 2]. Here the formula needs only dp[i - 1], which is curr. The second variable best is not part of the recurrence. It records the maximum over all states.

This is a general pattern. When the answer is the best of all dp[i] and not the last dp[i], keep a running maximum next to the rolling value.

The start is curr = best = nums[0]. The subarray must hold at least one number, so an empty run at 0 is not allowed. An array of only negative numbers has a negative answer. For [-3, -1, -2], the answer is -1.

The cost and the follow-ups

There are N states, and each does two max calls. The time is O(N) and the space is O(1).

The brute force checks every start and end, which is N * (N + 1) / 2 subarrays. For the nine numbers above, that is 45. The loop makes 8 passes.

Adding up each subarray from scratch costs O(N^3) in total. Keeping a running sum while the end moves right lowers it to O(N^2). Kadane's algorithm removes the loop over the start, so it is O(N).

To return the run and not only its sum, record start = i each time curr restarts at x. Record best_start and best_end each time best increases.

To find the best rectangle in a matrix of numbers, pick two rows r1 and r2. Add the numbers of each column from r1 to r2 into one array. The best run in that array is the best rectangle between those two rows, so run Kadane on it. There are O(rows^2) row pairs and each Kadane pass costs O(cols), so the total is O(rows^2 * cols).

For a circular array, as in Maximum Sum Circular Subarray (LC 918), the answer is the larger of two values. One is the normal Kadane result. The other is the total sum minus the smallest subarray sum. If every number is negative, return the normal result.

60-Second Pitch

"I define curr as the best sum of a subarray that ends at the current number. That subarray is either the number alone, or the earlier best run plus the number. So curr = max(x, curr + x). Every subarray ends somewhere, so the largest curr over all positions is the answer. I keep it in best. I start both from nums[0], because the subarray must contain at least one number. One pass gives O(N) time and O(1) space."

Common Questions

Why is it correct to drop the earlier run when it is negative?

A negative curr makes every subarray that includes it smaller than the same subarray without it. So curr + x is less than x. Starting at x is always at least as good. This is why curr = max(x, curr + x) is enough.

What does Kadane's algorithm return when all numbers are negative?

The largest single number. The start curr = best = nums[0] and the comparison with x allow this. For [-3, -1, -2], the answer is -1. A version that starts at 0 returns 0, which is wrong, because the subarray must have at least one number.

How do you return the subarray and not only the sum?

Add start. Set start = i when the run restarts at x. When best increases, save best_start = start and best_end = i. For the LeetCode example, the saved run is [4, -1, 2, 1].

How does this change for Maximum Product Subarray (LC 152)?

Keep two values, the largest and the smallest product that end at i. A negative x swaps their roles, because a negative number times the smallest product can become the largest. One value is not enough.

How do you solve Maximum Sum Circular Subarray (LC 918)?

The best run either stays inside the array, or wraps around. For a run that stays inside, use Kadane. For a run that wraps, take the total sum minus the smallest subarray sum, which is Kadane on the negated numbers. Return the larger value. If all numbers are negative, return the plain result, because the wrapping case would be an empty run.

SWE Reasoning

Spot it

  • The question asks for the best sum of one unbroken run of an array.
  • The run can start and end anywhere.
  • The numbers can be negative, so the whole array is not always the best run.
  • The brute force tries every start and end in O(N^2), and a one-pass answer is wanted.
  • Every subarray can be described by where it ends. This suggests the state "best sum ending at i".

The invariant, step by step

  1. Before the loop, curr and best are both nums[0]. The only subarray that ends at index 0 is nums[0].
  2. For the next number x, curr is the best sum of a subarray that ends just before x.
  3. curr = max(x, curr + x) picks the better of two forms: extend the run, or start fresh at x.
  4. Now curr is the best sum of a subarray that ends at x.
  5. best = max(best, curr) keeps the largest curr seen so far.
  6. After the last number, best covers every end index, so it is the answer.

Traps

  • Starting at 0. curr = best = 0 allows an empty subarray. For [-3, -1, -2], the result is 0 instead of -1.
  • Returning curr. curr is the best sum ending at the last number. For the LeetCode example, it is 5 and the answer is 6.
  • Comparing with 0. curr = max(0, curr + x) starts an empty run. For [-2, -1], the result is 0 instead of -1.
  • Start index never moves. To return the run, set start = i on every restart. If start stays at 0, the run begins too early and includes numbers that lower the sum.
  • Wrong problem. A product, or an exact target sum, does not work with this rule. A negative number can turn a small product into a large one, and an exact sum needs a different method.

Practice

Canonical LeetCode:Maximum Subarray (LC 53)Maximum Sum Circular Subarray (LC 918)Maximum Product Subarray (LC 152)Best Time to Buy and Sell Stock (LC 121)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•