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•Stack

Builds on Histogram Stack, which you haven’t finished yet.

P3MediumBuilt on Next Greater Element~4 min speedrun

Contribution Technique (Sum over All Subarrays via Each Element's Share)

Built on
Histogram StackEach popped item is the minimum of the range between its two smaller neighbours, so it counts value times left times right.

Sum of Subarray Minimums (LC 907)(opens leetcode.com)

You're given an array of integers arr. Look at every contiguous subarray b of arr, take its minimum value min(b), and add all of those minimums together. Because that total can get huge, return it modulo 109 + 7.

Example 1
Input:arr = [3,1,2,4]
Output:17
Subarrays are [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4], [3,1,2,4]. Minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1. Sum is 17.

Contribution Technique

all subarrays ➔ element i is the minimum of (i - left) × (right - i) of them ➔ sum of shares
Spot it when: You need a sum or count over all subarrays of their minimum (or maximum), and n is too large for O(n^2).
Contribution Technique
Python
1def sum_subarray_mins(arr: list[int]) -> int:
2 """Sum of Subarray Minimums: add up each element's share as the minimum.
3 Time: O(N) | Space: O(N)
4 """
5 MOD = 10**9 + 7
6 n = len(arr)
7 left = [-1] * n # Index of the previous strictly smaller element
8 right = [n] * n # Index of the next smaller-or-equal element
9 stack: list[int] = [] # Indices with increasing values from bottom to top
10 
11 # 1. Visit Every Index Left To Right:
12 # One pass finds both walls for every index, since each index's walls only depend on values already seen or seen as they arrive.
13 for i in range(n):
14 # 2. Resolve Every Index That arr[i] Beats Or Ties:
15 # While the top of the stack is not strictly smaller than arr[i], arr[i] is the first value to its right that is smaller or equal, so pop it and record i as its right wall. The >= (not >) is what sends a tie's right wall to the later copy.
16 while stack and arr[stack[-1]] >= arr[i]:
17 right[stack.pop()] = i # arr[i] is the first value <= the popped one
18 # 3. Record The Left Wall And Push i:
19 # After the pops above, whatever remains on top is strictly smaller than arr[i], so it is arr[i]'s left wall; i then joins the stack to serve as a future right wall.
20 left[i] = stack[-1] if stack else -1 # Top is strictly smaller
21 stack.append(i)
22 
23 # 4. Add Up Every Index's Share:
24 # arr[i] is the minimum of (i - left[i]) * (right[i] - i) subarrays, so multiplying and summing counts every subarray's minimum exactly once.
25 return sum(arr[i] * (i - left[i]) * (right[i] - i) for i in range(n)) % MOD
Invariant: The values at the indices in stack strictly increase from bottom to top, so the index under i is its previous strictly smaller element and the index that pops i is its next smaller-or-equal one.

Instead of listing every subarray, ask how many subarrays have arr[i] as their minimum: it stays the minimum until the first smaller value on each side, at left[i] and right[i]. That gives (i - left[i]) * (right[i] - i) subarrays, and one increasing stack finds both boundaries as values are popped. For equal values, stop at a strictly smaller value on one side and a smaller-or-equal value on the other (the >= in the pop), or subarrays with ties are counted twice.

See it run

Contribution Technique, step by step

stack [] · left [-1, -1, -1, -1] · right [4, 4, 4, 4]

stack [0] · left [-1, -1, -1, -1] · right [4, 4, 4, 4]

stack [] · left [-1, -1, -1, -1] · right [1, 4, 4, 4]

stack [1] · left [-1, -1, -1, -1] · right [1, 4, 4, 4]

stack [1, 2] · left [-1, -1, 1, -1] · right [1, 4, 4, 4]

stack [1] · left [-1, -1, 1, -1] · right [1, 4, 3, 4]

stack [] · left [-1, -1, 1, -1] · right [1, 3, 3, 4]

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4]

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 3

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 7

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 9

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 13

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · answer 13

The top row is arr. The column number is the index. The bottom row is the stack: the left cell is the bottom, each cell holds an index, and a blank cell is an unused slot. A wall of -1, or a wall equal to the length of arr, has no cell, because it is outside arr.

  • i the index being read now
  • popped the index taken off the stack, whose right wall is written now
  • left wall the index in left[i], a strictly smaller value on the left
  • right wall the index in right[i], a smaller or equal value on the right
  • pushed the index that was just pushed

Start: stack is empty, every left is -1 and every right is 4.

The stack is empty, so left[0] = -1. Push 0.

arr[0] = 3 is at least arr[1] = 1, so pop 0. right[0] = 1.

The stack is empty, so left[1] = -1. Push 1.

The top is 1 and arr[1] = 1 is below 2, so left[2] = 1. Push 2.

arr[2] = 2 is at least arr[3] = 1, so pop 2. right[2] = 3.

Tie: arr[1] = 1 equals arr[3] = 1, and >= pops it. right[1] = 3 sends the tie to the later copy.

Equal values do not stop the left side, so left[3] = -1. Push 3.

arr[0] = 3 is the minimum of (0 - (-1)) * (1 - 0) = 1 subarrays, so it adds 3.

arr[1] = 1 is the minimum of (1 - (-1)) * (3 - 1) = 4 subarrays, so it adds 4.

arr[2] = 2 is the minimum of (2 - 1) * (3 - 2) = 1 subarrays, so it adds 2.

arr[3] = 1 is the minimum of (3 - (-1)) * (4 - 3) = 4 subarrays, so it adds 4.

Done: the shares add up to 13, so the answer is 13.

arr
03
11
22
31
stack
1 / 13
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Stopping at a strictly smaller value on both sides

    Mistake
    for i in range(n - 1, -1, -1):
        while stack and arr[stack[-1]] >= arr[i]:
            stack.pop()
        right[i] = stack[-1] if stack else n
    Fix
    while stack and arr[stack[-1]] >= arr[i]:
        right[stack.pop()] = i

    What happens: Take a subarray in which two equal values are both the minimum. If both sides stop only at a strictly smaller value, each of the two claims that subarray. It is then counted twice. The blueprint stops at a smaller or equal value on the right, through the >= in the pop. So only one of the equal values claims it.

    Example: arr = [1, 1]
    Mistake: returns 4Fix: returns 3
  2. 2

    Gotcha 2: Defaulting right to n - 1 instead of n

    Mistake
    right = [n - 1] * n
    Fix
    right = [n] * n

    What happens: right[i] is the first index where arr[i] stops being the minimum. When no smaller or equal value follows, arr[i] stays the minimum up to the last index, n - 1. So the boundary is one past it, n. With n - 1, every such value loses the subarrays that end at the last index.

    Example: arr = [3, 1, 2, 4]
    Mistake: returns 9Fix: returns 17
  3. 3

    Gotcha 3: Leaving out the final % MOD

    Mistake
    return sum(arr[i] * (i - left[i]) * (right[i] - i) for i in range(n))
    Fix
    return sum(arr[i] * (i - left[i]) * (right[i] - i) for i in range(n)) % MOD

    What happens: Sum of Subarray Minimums (LC 907) asks for the total modulo 10**9 + 7. The total can be far larger than that. Python ints never overflow, so one % MOD at the end is enough. In Java or C++, each product can pass 231, so compute it in 64 bits.

    Example: arr = [30000] * 400
    Mistake: returns 2406000000Fix: returns 405999986
Not this one if…
  • You want one best rectangle (height times width), as in Largest Rectangle in Histogram (LC 84), not a total over every subarray. See Histogram Stack

Deep Dive

From listing subarrays to counting shares

Sum of Subarray Minimums (LC 907) asks for the sum of the minimum of every subarray, modulo 10**9 + 7. An array of length N has N * (N + 1) / 2 subarrays, so listing them is too slow for large N.

Count by element instead. For each arr[i], count the subarrays whose minimum it is. That minimum value is added once for each of them, so its share is arr[i] times that count.

arr[i] stays the minimum until a value that beats it. Let left[i] be the index of the previous strictly smaller value, or -1. Let right[i] be the index of the next smaller or equal value, or n. A subarray starts at one of the i - left[i] indexes after left[i], up to i. It ends at one of the right[i] - i indexes from i before right[i]. So the count is (i - left[i]) * (right[i] - i).

What this card adds to Monotonic Stack (Largest Rectangle in Histogram): the popped item is the minimum of the range between its two walls, and the code multiplies value by left count by right count and does not need an area or a width test.

Example: arr = [3, 1, 2, 4]. The shares are 3 * 1 * 1 = 3, 1 * 2 * 3 = 6, 2 * 1 * 2 = 4 and 4 * 1 * 1 = 4. The sum is 17, which is the answer.

One trace: the scan finds both walls, then each index adds its share

The board replays arr = [3, 1, 2, 1], which has a tie. The bottom row is stack, and each cell holds an index.

The scan visits index 1 (value 1) first with a pop. It pops index 0 and writes right[0] = 1. The stack is then empty, so left[1] = -1.

Index 2 (value 2) pops nothing. The top, index 1, is smaller, so left[2] = 1. Index 3 (value 1) pops index 2 and writes right[2] = 3. Then it pops index 1, which has an equal value, and writes right[1] = 3.

The stack is empty after that, so left[3] = -1. The shares are 3 * 1 * 1 = 3, 1 * 2 * 2 = 4, 2 * 1 * 1 = 2 and 1 * 4 * 1 = 4. The total is 13. The subarray [1, 2, 1] is counted once, by index 3.

The trap: strict on one side, non-strict on the other

Two equal values can both be the minimum of one subarray. The count must give that subarray to exactly one of them. The rule is: stop at a strictly smaller value on one side, and at a smaller or equal value on the other side.

In the blueprint, while stack and arr[stack[-1]] >= arr[i] pops an equal value. So the right wall is the next smaller or equal value, and the left wall is strictly smaller. The earlier copy gives up the subarrays that reach the later copy.

If both sides stop only at a strictly smaller value, both copies claim the same subarrays. For arr = [1, 1] the correct answer is 3, because the minimums are 1, 1 and 1. Counting both sides as strict gives 4.

Either side may be the strict one. The sides must be opposite.

Defaults, the modulus, and the cost

Start with right = [n] * n. right[i] is the first index where arr[i] stops being the minimum. If no smaller or equal value follows, the minimum holds up to index n - 1, so the wall is n. With n - 1, those values lose the subarrays that end at the last index. For [3, 1, 2, 4] the result is 9 and not 17.

Take the modulus once, at the end: % MOD. Python integers do not overflow. In Java or C++ a product such as arr[i] * a * b can pass the 32-bit limit, so compute it in 64 bits.

The time is O(N), because every index is pushed once and popped at most once, and the final sum is one pass. The space is O(N) for left, right and stack.

For the sum of maximums, flip the comparison. Sum of Subarray Ranges (LC 2104) is the sum of maximums minus the sum of minimums, so it needs this scan twice.

60-Second Pitch

"Listing all subarrays is O(N^2), so I count by element. For each value I count how many subarrays have it as their minimum. That count is the number of start positions times the number of end positions. The start positions run back to the previous strictly smaller value. The end positions run forward to the next smaller or equal value. One increasing stack finds both walls in a single pass. To avoid counting ties twice, I use a strict comparison on the left and a non-strict comparison on the right. I add value times left count times right count for every index, modulo 10**9 + 7. The time is O(N) and the space is O(N)."

Common Questions

Why does counting by element work?

Every subarray has exactly one minimum value, and the tie rule gives it to exactly one index. So every subarray is counted once. The sum of the shares of all indexes is the sum of the minimums of all subarrays.

How do you avoid counting equal values twice?

Make one side strict and the other side non-strict. The code pops with >=, so the right wall is the next smaller or equal value. The left wall is the previous strictly smaller value. Then a subarray with two equal minimums belongs only to the later copy.

What are the time and space complexity?

O(N) time. Every index is pushed once and popped at most once, and the final sum is one pass. O(N) space for left, right and stack.

How would you compute the sum of subarray maximums?

Flip the comparison: pop while arr[stack[-1]] <= arr[i]. The rest stays the same. Sum of Subarray Ranges (LC 2104) subtracts the sum of minimums from the sum of maximums.

SWE Reasoning

Spot it

  • The answer is a sum or a count over all subarrays of their minimum or maximum.
  • N is large, so an O(N^2) loop over all subarrays is too slow.
  • The result is asked modulo a large number such as 10**9 + 7.
  • Each element's effect depends only on how far it stays the minimum to the left and to the right.
  • The array can contain equal values, so the tie rule matters.

The invariant, step by step

  1. Each subarray has one minimum value. Give each subarray to exactly one index that holds it.
  2. For index i, left[i] is the previous strictly smaller value, and right[i] is the next smaller or equal value.
  3. A subarray belongs to i when it starts after left[i], ends before right[i], and contains i. There are (i - left[i]) * (right[i] - i) of them.
  4. One scan with a stack finds both walls. The values at the indexes in stack strictly increase from bottom to top.
  5. When arr[i] pops an index, i is that index's right wall. After the pops, the top of stack is the left wall of i.
  6. Add arr[i] * (i - left[i]) * (right[i] - i) for every index, then take the result modulo MOD.

Traps

  • Strict on both sides. Subarrays with equal minimums are counted twice. Use >= in the pop and a strict left wall.
  • right = [n - 1] * n. Subarrays that end at the last index are lost. Use n.
  • No % MOD. The result is too large. Take the modulus at the end.
  • Overflow in other languages. Compute the product in 64 bits.
  • Looking for the subarray itself. Never build subarrays. Count by element.

Practice

Canonical LeetCode:Sum of Subarray Minimums (LC 907)Sum of Subarray Ranges (LC 2104)Sum of Total Strength of Wizards (LC 2281)Count Unique Characters of All Substrings of a Given String (LC 828)Total Appeal of A String (LC 2262)
Theory Context•Stack

Builds on Histogram Stack, which you haven’t finished yet.

P3MediumBuilt on Next Greater Element~4 min speedrun

Contribution Technique (Sum over All Subarrays via Each Element's Share)

Built on
Histogram StackEach popped item is the minimum of the range between its two smaller neighbours, so it counts value times left times right.

Sum of Subarray Minimums (LC 907)(opens leetcode.com)

You're given an array of integers arr. Look at every contiguous subarray b of arr, take its minimum value min(b), and add all of those minimums together. Because that total can get huge, return it modulo 109 + 7.

Example 1
Input:arr = [3,1,2,4]
Output:17
Subarrays are [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4], [3,1,2,4]. Minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1. Sum is 17.

Contribution Technique

all subarrays ➔ element i is the minimum of (i - left) × (right - i) of them ➔ sum of shares
Spot it when: You need a sum or count over all subarrays of their minimum (or maximum), and n is too large for O(n^2).
Contribution Technique
Python
1def sum_subarray_mins(arr: list[int]) -> int:
2 """Sum of Subarray Minimums: add up each element's share as the minimum.
3 Time: O(N) | Space: O(N)
4 """
5 MOD = 10**9 + 7
6 n = len(arr)
7 left = [-1] * n # Index of the previous strictly smaller element
8 right = [n] * n # Index of the next smaller-or-equal element
9 stack: list[int] = [] # Indices with increasing values from bottom to top
10 
11 # 1. Visit Every Index Left To Right:
12 # One pass finds both walls for every index, since each index's walls only depend on values already seen or seen as they arrive.
13 for i in range(n):
14 # 2. Resolve Every Index That arr[i] Beats Or Ties:
15 # While the top of the stack is not strictly smaller than arr[i], arr[i] is the first value to its right that is smaller or equal, so pop it and record i as its right wall. The >= (not >) is what sends a tie's right wall to the later copy.
16 while stack and arr[stack[-1]] >= arr[i]:
17 right[stack.pop()] = i # arr[i] is the first value <= the popped one
18 # 3. Record The Left Wall And Push i:
19 # After the pops above, whatever remains on top is strictly smaller than arr[i], so it is arr[i]'s left wall; i then joins the stack to serve as a future right wall.
20 left[i] = stack[-1] if stack else -1 # Top is strictly smaller
21 stack.append(i)
22 
23 # 4. Add Up Every Index's Share:
24 # arr[i] is the minimum of (i - left[i]) * (right[i] - i) subarrays, so multiplying and summing counts every subarray's minimum exactly once.
25 return sum(arr[i] * (i - left[i]) * (right[i] - i) for i in range(n)) % MOD
Invariant: The values at the indices in stack strictly increase from bottom to top, so the index under i is its previous strictly smaller element and the index that pops i is its next smaller-or-equal one.

Instead of listing every subarray, ask how many subarrays have arr[i] as their minimum: it stays the minimum until the first smaller value on each side, at left[i] and right[i]. That gives (i - left[i]) * (right[i] - i) subarrays, and one increasing stack finds both boundaries as values are popped. For equal values, stop at a strictly smaller value on one side and a smaller-or-equal value on the other (the >= in the pop), or subarrays with ties are counted twice.

See it run

Contribution Technique, step by step

stack [] · left [-1, -1, -1, -1] · right [4, 4, 4, 4]

stack [0] · left [-1, -1, -1, -1] · right [4, 4, 4, 4]

stack [] · left [-1, -1, -1, -1] · right [1, 4, 4, 4]

stack [1] · left [-1, -1, -1, -1] · right [1, 4, 4, 4]

stack [1, 2] · left [-1, -1, 1, -1] · right [1, 4, 4, 4]

stack [1] · left [-1, -1, 1, -1] · right [1, 4, 3, 4]

stack [] · left [-1, -1, 1, -1] · right [1, 3, 3, 4]

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4]

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 3

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 7

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 9

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · total 13

stack [3] · left [-1, -1, 1, -1] · right [1, 3, 3, 4] · answer 13

The top row is arr. The column number is the index. The bottom row is the stack: the left cell is the bottom, each cell holds an index, and a blank cell is an unused slot. A wall of -1, or a wall equal to the length of arr, has no cell, because it is outside arr.

  • i the index being read now
  • popped the index taken off the stack, whose right wall is written now
  • left wall the index in left[i], a strictly smaller value on the left
  • right wall the index in right[i], a smaller or equal value on the right
  • pushed the index that was just pushed

Start: stack is empty, every left is -1 and every right is 4.

The stack is empty, so left[0] = -1. Push 0.

arr[0] = 3 is at least arr[1] = 1, so pop 0. right[0] = 1.

The stack is empty, so left[1] = -1. Push 1.

The top is 1 and arr[1] = 1 is below 2, so left[2] = 1. Push 2.

arr[2] = 2 is at least arr[3] = 1, so pop 2. right[2] = 3.

Tie: arr[1] = 1 equals arr[3] = 1, and >= pops it. right[1] = 3 sends the tie to the later copy.

Equal values do not stop the left side, so left[3] = -1. Push 3.

arr[0] = 3 is the minimum of (0 - (-1)) * (1 - 0) = 1 subarrays, so it adds 3.

arr[1] = 1 is the minimum of (1 - (-1)) * (3 - 1) = 4 subarrays, so it adds 4.

arr[2] = 2 is the minimum of (2 - 1) * (3 - 2) = 1 subarrays, so it adds 2.

arr[3] = 1 is the minimum of (3 - (-1)) * (4 - 3) = 4 subarrays, so it adds 4.

Done: the shares add up to 13, so the answer is 13.

arr
03
11
22
31
stack
1 / 13
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Stopping at a strictly smaller value on both sides

    Mistake
    for i in range(n - 1, -1, -1):
        while stack and arr[stack[-1]] >= arr[i]:
            stack.pop()
        right[i] = stack[-1] if stack else n
    Fix
    while stack and arr[stack[-1]] >= arr[i]:
        right[stack.pop()] = i

    What happens: Take a subarray in which two equal values are both the minimum. If both sides stop only at a strictly smaller value, each of the two claims that subarray. It is then counted twice. The blueprint stops at a smaller or equal value on the right, through the >= in the pop. So only one of the equal values claims it.

    Example: arr = [1, 1]
    Mistake: returns 4Fix: returns 3
  2. 2

    Gotcha 2: Defaulting right to n - 1 instead of n

    Mistake
    right = [n - 1] * n
    Fix
    right = [n] * n

    What happens: right[i] is the first index where arr[i] stops being the minimum. When no smaller or equal value follows, arr[i] stays the minimum up to the last index, n - 1. So the boundary is one past it, n. With n - 1, every such value loses the subarrays that end at the last index.

    Example: arr = [3, 1, 2, 4]
    Mistake: returns 9Fix: returns 17
  3. 3

    Gotcha 3: Leaving out the final % MOD

    Mistake
    return sum(arr[i] * (i - left[i]) * (right[i] - i) for i in range(n))
    Fix
    return sum(arr[i] * (i - left[i]) * (right[i] - i) for i in range(n)) % MOD

    What happens: Sum of Subarray Minimums (LC 907) asks for the total modulo 10**9 + 7. The total can be far larger than that. Python ints never overflow, so one % MOD at the end is enough. In Java or C++, each product can pass 231, so compute it in 64 bits.

    Example: arr = [30000] * 400
    Mistake: returns 2406000000Fix: returns 405999986
Not this one if…
  • You want one best rectangle (height times width), as in Largest Rectangle in Histogram (LC 84), not a total over every subarray. See Histogram Stack

Deep Dive

From listing subarrays to counting shares

Sum of Subarray Minimums (LC 907) asks for the sum of the minimum of every subarray, modulo 10**9 + 7. An array of length N has N * (N + 1) / 2 subarrays, so listing them is too slow for large N.

Count by element instead. For each arr[i], count the subarrays whose minimum it is. That minimum value is added once for each of them, so its share is arr[i] times that count.

arr[i] stays the minimum until a value that beats it. Let left[i] be the index of the previous strictly smaller value, or -1. Let right[i] be the index of the next smaller or equal value, or n. A subarray starts at one of the i - left[i] indexes after left[i], up to i. It ends at one of the right[i] - i indexes from i before right[i]. So the count is (i - left[i]) * (right[i] - i).

What this card adds to Monotonic Stack (Largest Rectangle in Histogram): the popped item is the minimum of the range between its two walls, and the code multiplies value by left count by right count and does not need an area or a width test.

Example: arr = [3, 1, 2, 4]. The shares are 3 * 1 * 1 = 3, 1 * 2 * 3 = 6, 2 * 1 * 2 = 4 and 4 * 1 * 1 = 4. The sum is 17, which is the answer.

One trace: the scan finds both walls, then each index adds its share

The board replays arr = [3, 1, 2, 1], which has a tie. The bottom row is stack, and each cell holds an index.

The scan visits index 1 (value 1) first with a pop. It pops index 0 and writes right[0] = 1. The stack is then empty, so left[1] = -1.

Index 2 (value 2) pops nothing. The top, index 1, is smaller, so left[2] = 1. Index 3 (value 1) pops index 2 and writes right[2] = 3. Then it pops index 1, which has an equal value, and writes right[1] = 3.

The stack is empty after that, so left[3] = -1. The shares are 3 * 1 * 1 = 3, 1 * 2 * 2 = 4, 2 * 1 * 1 = 2 and 1 * 4 * 1 = 4. The total is 13. The subarray [1, 2, 1] is counted once, by index 3.

The trap: strict on one side, non-strict on the other

Two equal values can both be the minimum of one subarray. The count must give that subarray to exactly one of them. The rule is: stop at a strictly smaller value on one side, and at a smaller or equal value on the other side.

In the blueprint, while stack and arr[stack[-1]] >= arr[i] pops an equal value. So the right wall is the next smaller or equal value, and the left wall is strictly smaller. The earlier copy gives up the subarrays that reach the later copy.

If both sides stop only at a strictly smaller value, both copies claim the same subarrays. For arr = [1, 1] the correct answer is 3, because the minimums are 1, 1 and 1. Counting both sides as strict gives 4.

Either side may be the strict one. The sides must be opposite.

Defaults, the modulus, and the cost

Start with right = [n] * n. right[i] is the first index where arr[i] stops being the minimum. If no smaller or equal value follows, the minimum holds up to index n - 1, so the wall is n. With n - 1, those values lose the subarrays that end at the last index. For [3, 1, 2, 4] the result is 9 and not 17.

Take the modulus once, at the end: % MOD. Python integers do not overflow. In Java or C++ a product such as arr[i] * a * b can pass the 32-bit limit, so compute it in 64 bits.

The time is O(N), because every index is pushed once and popped at most once, and the final sum is one pass. The space is O(N) for left, right and stack.

For the sum of maximums, flip the comparison. Sum of Subarray Ranges (LC 2104) is the sum of maximums minus the sum of minimums, so it needs this scan twice.

60-Second Pitch

"Listing all subarrays is O(N^2), so I count by element. For each value I count how many subarrays have it as their minimum. That count is the number of start positions times the number of end positions. The start positions run back to the previous strictly smaller value. The end positions run forward to the next smaller or equal value. One increasing stack finds both walls in a single pass. To avoid counting ties twice, I use a strict comparison on the left and a non-strict comparison on the right. I add value times left count times right count for every index, modulo 10**9 + 7. The time is O(N) and the space is O(N)."

Common Questions

Why does counting by element work?

Every subarray has exactly one minimum value, and the tie rule gives it to exactly one index. So every subarray is counted once. The sum of the shares of all indexes is the sum of the minimums of all subarrays.

How do you avoid counting equal values twice?

Make one side strict and the other side non-strict. The code pops with >=, so the right wall is the next smaller or equal value. The left wall is the previous strictly smaller value. Then a subarray with two equal minimums belongs only to the later copy.

What are the time and space complexity?

O(N) time. Every index is pushed once and popped at most once, and the final sum is one pass. O(N) space for left, right and stack.

How would you compute the sum of subarray maximums?

Flip the comparison: pop while arr[stack[-1]] <= arr[i]. The rest stays the same. Sum of Subarray Ranges (LC 2104) subtracts the sum of minimums from the sum of maximums.

SWE Reasoning

Spot it

  • The answer is a sum or a count over all subarrays of their minimum or maximum.
  • N is large, so an O(N^2) loop over all subarrays is too slow.
  • The result is asked modulo a large number such as 10**9 + 7.
  • Each element's effect depends only on how far it stays the minimum to the left and to the right.
  • The array can contain equal values, so the tie rule matters.

The invariant, step by step

  1. Each subarray has one minimum value. Give each subarray to exactly one index that holds it.
  2. For index i, left[i] is the previous strictly smaller value, and right[i] is the next smaller or equal value.
  3. A subarray belongs to i when it starts after left[i], ends before right[i], and contains i. There are (i - left[i]) * (right[i] - i) of them.
  4. One scan with a stack finds both walls. The values at the indexes in stack strictly increase from bottom to top.
  5. When arr[i] pops an index, i is that index's right wall. After the pops, the top of stack is the left wall of i.
  6. Add arr[i] * (i - left[i]) * (right[i] - i) for every index, then take the result modulo MOD.

Traps

  • Strict on both sides. Subarrays with equal minimums are counted twice. Use >= in the pop and a strict left wall.
  • right = [n - 1] * n. Subarrays that end at the last index are lost. Use n.
  • No % MOD. The result is too large. Take the modulus at the end.
  • Overflow in other languages. Compute the product in 64 bits.
  • Looking for the subarray itself. Never build subarrays. Count by element.

Practice

Canonical LeetCode:Sum of Subarray Minimums (LC 907)Sum of Subarray Ranges (LC 2104)Sum of Total Strength of Wizards (LC 2281)Count Unique Characters of All Substrings of a Given String (LC 828)Total Appeal of A String (LC 2262)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•