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.

P1MediumMental model~6 min speedrun

0/1 Knapsack (Partition Equal Subset Sum, Subset Sum)

Unlocks
Bitmask DP
The problem

Partition Equal Subset Sum (LC 416)(opens leetcode.com)

You're given an integer array nums. Decide whether it can be split into two subsets whose element sums equal each other -- return true if such a split exists and false otherwise.

Example 1
Input:nums = [1,5,11,5]
Output:true
The array can be partitioned as [1, 5, 5] and [11].

0/1 Knapsack

items & capacities ➔ capacity loop runs downward so each item counts once ➔ dp[w]
Spot it when: You pick items that each count at most once, and the chosen sizes must add up to a target: equal partitions, subset sums, a capacity that each item fills once.
0/1 Knapsack
Python
1def can_partition(nums: list[int]) -> bool:
2 """0/1 Knapsack: Partition array into two equal subsets (reverse loop).
3 Time: O(N * Target) | Space: O(Target)
4 """
5 total = sum(nums)
6 if total % 2 != 0:
7 return False
8 target = total // 2
9 # dp: dp[w] is True when sum w is reachable using only the items processed so far
10 dp = [False] * (target + 1)
11 dp[0] = True
12 for x in nums:
13 for w in range(target, x - 1, -1):
14 # 1. Use This Item At Most Once:
15 # The reverse loop reads dp[w - x] before this pass updates it, so x is counted at most once per w.
16 dp[w] = dp[w] or dp[w - x]
17 return dp[target]
Invariant: After each item x, dp[w] is True when some chosen items among those processed so far add up to exactly w.

Treat each sum as its own subproblem: dp[w] is True when the items seen so far can add up to w, and each item x updates it from dp[w - x]. Loop w downward, so dp[w - x] has not been touched by the current x yet and x is counted at most once; looping upward would let the same x fill dp[w - x] and then dp[w] in one pass. The trap is exactly that loop direction: for w in range(target, x - 1, -1), not range(x, target + 1).

See it run

Partition Equal Subset Sum, step by step

x - · w - · dp[target] False

x 1 · w - · dp[target] False

x 1 · w 1 · dp[target] False

x 5 · w - · dp[target] False

x 5 · w 6 · dp[target] False

x 5 · w 5 · dp[target] False

x 11 · w - · dp[target] False

x 11 · w 11 · dp[target] True

x 5 · w - · dp[target] True

x 5 · w 11 · dp[target] True

x 5 · w 10 · dp[target] True

x 5 · w 6 · dp[target] True

x 5 · w 5 · dp[target] True

x - · w - · dp[target] True

Row k is the list dp after the k-th item. Column w is the sum w. T means True and F means False.

  • write the cell being written
  • read the cell dp[w - x], read before this item changes it
  • answer the final answer

Start: nums = [1, 5, 11, 5], total = 22, target = 11. dp[0] = True because the sum 0 needs no item. Every other cell is False.

Item x = 1. The loop runs w from 11 down to 1. Only sums where dp[w - 1] is True can change, so only those are shown.

w = 1: dp[0] is True and does not use item 1 yet. So dp[1] becomes True.

Item x = 5. The loop runs w from 11 down to 5. Only sums where dp[w - 5] is True can change, so only those are shown.

w = 6: dp[1] is True and does not use item 5 yet. So dp[6] becomes True.

w = 5: dp[0] is True and does not use item 5 yet. So dp[5] becomes True.

Item x = 11. The loop runs w from 11 down to 11. Only sums where dp[w - 11] is True can change, so only those are shown.

w = 11: dp[0] is True and does not use item 11 yet. So dp[11] becomes True.

Item x = 5. The loop runs w from 11 down to 5. Only sums where dp[w - 5] is True can change, so only those are shown.

w = 11: dp[6] is True, but dp[11] is already True, so it stays True.

w = 10: dp[5] is True and does not use item 5 yet. So dp[10] becomes True.

w = 6: dp[1] is True, but dp[6] is already True, so it stays True.

w = 5: dp[0] is True, but dp[5] is already True, so it stays True.

All items are processed. dp[11] is True, so the array can be split into two equal halves.

start
0Twrite
1F
2F
3F
4F
5F
6F
7F
8F
9F
10F
11F
x = 1
x = 5
x = 11
x = 5
1 / 14
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Looping w upward in a 0/1 problem

    Mistake
    for w in range(x, target + 1):
    Fix
    for w in range(target, x - 1, -1):

    What happens: Going upward, dp[w - x] may already include the current x. Then dp[w] uses x a second time. Here x = 1 makes every sum from 1 to 4 reachable, so the target 4 looks reachable. Going downward, dp[w - x] still describes only the items before x.

    Example: nums = [1, 2, 5]
    Mistake: returns TrueFix: returns False
  2. 2

    Gotcha 2: Skipping the check for an odd total

    Mistake
    target = total // 2
    Fix
    if total % 2 != 0:
        return False
    target = total // 2

    What happens: Two equal halves need an even total. For an odd total, total // 2 rounds down. The search then finds a subset for that smaller target. So it returns True for a split that is impossible.

    Example: nums = [1, 2]
    Mistake: returns TrueFix: returns False
  3. 3

    Gotcha 3: Starting dp with every sum reachable

    Mistake
    dp = [True] * (target + 1)
    Fix
    dp = [False] * (target + 1)

    What happens: dp[w] is True only when some chosen items add up to exactly w. Before any item is processed, only the sum 0 is reachable, so dp[0] is True and every other cell is False. If every cell starts True, every target looks reachable.

    Example: nums = [1, 2, 5]
    Mistake: returns TrueFix: returns False
Not this one if…
  • Each item can be used any number of times, as in Coin Change (LC 322): the capacity loop runs forward, so an item may fill dp[w - item] and then dp[w] in the same pass. See Unbounded Knapsack
  • You must return every combination itself, not just whether a sum is reachable, as in Combination Sum (LC 39): a table of booleans per sum can't list them. See Combinations

Deep Dive

The state is a sum, and the items arrive one at a time

In a 0/1 knapsack problem you choose items, and each item is either taken once or left out. The state is a sum w. In can_partition, dp[w] is True when some of the items processed so far add up to exactly w.

Processing items in order is what makes the table correct. After item k, dp describes only the first k items. The next item x adds one choice: take x once, so dp[w] also becomes True when dp[w - x] was True before x.

At the start, only the sum 0 is reachable, by choosing no item. So dp[0] is True and every other cell is False. If every cell started True, every target would look reachable.

Example: for nums = [1, 5, 11, 5] the target is 11. The reachable sums are [0, 1] after the first item, [0, 1, 5, 6] after the second, [0, 1, 5, 6, 11] after 11, and [0, 1, 5, 6, 10, 11] after the last 5. The target 11 is reachable, so the answer is True.

Why the loop runs downward

Think of a table with one row per item, where dp[i][w] = dp[i - 1][w] or dp[i - 1][w - x]. Both terms read row i - 1. The code compresses the table to one array, so dp[w - x] must still hold the value from before item x.

Looping w downward gives exactly that. When the loop updates dp[w], the smaller index w - x has not changed yet in this pass. So x is used at most once.

Looping w upward does the opposite. dp[w - x] was already updated in this pass, so it may already include x, and dp[w] uses x a second time.

Example: can_partition([2, 6]) has total 8 and target 4, and the answer is False. If the loop runs upward, dp[2] becomes True, then dp[4] reads dp[2] and becomes True. The single 2 was used twice. The downward loop returns False.

Why the total must be even

Two equal halves need an even total. The code checks total % 2 != 0 first and returns False.

Without the check, total // 2 rounds down. For nums = [1, 2] the total is 3, and the rounded target is 1. The subset [1] reaches 1, so the code would return True for a split that does not exist.

The same idea turns other problems into this one. In Last Stone Weight II (LC 1049), splitting the stones into two groups with the smallest weight difference means finding the largest reachable sum that is at most half of the total. For [2, 7, 4, 1, 8, 1] the answer is 1. In Target Sum (LC 494), choosing plus or minus signs turns into counting the subsets that add up to (total + target) / 2. The table then holds counts, and dp[0] starts at 1.

The cost: states times work per state, and why it is pseudo-polynomial

There are N items and target sums, and each update is one comparison. The time is O(N * Target). The space is O(Target) for the single array.

The cost depends on the value of target, not on how many digits it has. A target of 10^9 would need a billion-entry array. This is called pseudo-polynomial time: it is polynomial in the number's value but exponential in the number of bits that write it. So check the constraints first. In Partition Equal Subset Sum (LC 416), the total is at most 20,000.

A second limit can be a pair. In Ones and Zeroes (LC 474), each string uses some zeros and some ones, and two budgets limit the choice. The state becomes the pair (zeros, ones), and both loops run downward. The time is O(N * M * K) for M zeros and K ones. For ["10", "0001", "111001", "1", "0"] with 5 zeros and 3 ones, the answer is 4.

60-Second Pitch

"The brute force tries every subset of the items, which is 2 to the power N subsets. For an equal split, the total must be even, and I look for a subset that adds up to half of it. I define dp[w] as True when some of the items seen so far add up to w. Only dp[0] starts True. Each item x sets dp[w] from dp[w - x]. I loop w downward, so dp[w - x] has not been touched by this item, and the item is used at most once. There are N items and up to total over 2 sums, so the time is O(N * Target). The space is O(Target). The time depends on the value of the target, so I check that the target is small before I choose this method."

Common Questions

Why is the 0/1 knapsack loop downward?

The one-dimensional array holds one row of the table. The update must read the row before the current item. A downward loop reaches the smaller index w - x before it is changed in this pass, so it still holds the old value. An upward loop would read a value that may already include x.

What are the time and space complexity, and is it polynomial?

O(N * Target) time and O(Target) space. It is pseudo-polynomial: it grows with the value of target, not with the number of digits of target. For a target near 10^9, the array would not fit in memory.

How do you recover which items were chosen?

A one-dimensional array loses that information. Keep the two-dimensional table dp[i][w] and walk back from the last row. If dp[i][w] equals dp[i - 1][w], item i was skipped. Otherwise item i was taken, and w decreases by its size. This costs O(N * Target) space.

How would you count the subsets instead of asking whether one exists?

Make dp hold counts, with dp[0] = 1 and dp[w] += dp[w - x] in the same downward loop. This is the step used in Target Sum (LC 494) after the signs are turned into a subset sum.

How would you handle a very large target?

First check whether the numbers have more structure. If the number of items is small, about 40 or fewer, a meet-in-the-middle search can work. It lists the subset sums of each half and combines them. If the target is large but the items are small, the table can sometimes use a bitset, where one bit stands for each reachable sum. Say which constraint you rely on.

SWE Reasoning

Spot it

  • You choose items, and each item is taken at most once. The chosen sizes must add up to a target, or stay within a budget.
  • The question is yes or no, a count of subsets, or the best value: can the target be made, how many subsets, how large a sum.
  • The target is small enough to be an array index, usually below about 20,000.
  • A problem about two equal groups, or the smallest difference between two groups, can be reduced to a subset sum of half the total.
  • The statement says each item is used once. That picks the downward loop.

The invariant, step by step

  1. The total is checked first. If it is odd, the code returns False. Otherwise target = total // 2.
  2. dp[0] is True and every other cell is False. Only the sum 0 is reachable before any item.
  3. The outer loop takes one item x. At this moment dp[w] is correct for all items before it.
  4. The inner loop visits each sum w from target down to x.
  5. The update dp[w] = dp[w] or dp[w - x] reads dp[w - x], which the downward loop has not changed yet in this pass. So x is counted at most once.
  6. When the outer loop ends, dp[w] is True when some chosen items add up to exactly w, which is the card's invariant. The code returns dp[target].

Traps

  • Loop upward. One item is used more than once. can_partition([1, 2, 5]) returns True instead of False. With x = 1, every sum from 1 to 4 looks reachable.
  • Skip the odd-total check. total // 2 rounds down, and the search finds a subset for the smaller target. can_partition([1, 2]) returns True instead of False.
  • Start dp with every sum reachable. If every cell starts True, every target looks reachable. can_partition([1, 2, 5]) returns True instead of False.
  • Forget dp[0] = True. Then no sum is ever reachable, and the answer is always False.
  • Use a huge target. An array of size target + 1 is too large when the target is around 10^9. Check the constraint before choosing this method.

Practice

Canonical LeetCode:Partition Equal Subset Sum (LC 416)Last Stone Weight II (LC 1049)Target Sum (LC 494)Ones and Zeroes (LC 474)
Theory Context•Dynamic Programming

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

P1MediumMental model~6 min speedrun

0/1 Knapsack (Partition Equal Subset Sum, Subset Sum)

Unlocks
Bitmask DP
The problem

Partition Equal Subset Sum (LC 416)(opens leetcode.com)

You're given an integer array nums. Decide whether it can be split into two subsets whose element sums equal each other -- return true if such a split exists and false otherwise.

Example 1
Input:nums = [1,5,11,5]
Output:true
The array can be partitioned as [1, 5, 5] and [11].

0/1 Knapsack

items & capacities ➔ capacity loop runs downward so each item counts once ➔ dp[w]
Spot it when: You pick items that each count at most once, and the chosen sizes must add up to a target: equal partitions, subset sums, a capacity that each item fills once.
0/1 Knapsack
Python
1def can_partition(nums: list[int]) -> bool:
2 """0/1 Knapsack: Partition array into two equal subsets (reverse loop).
3 Time: O(N * Target) | Space: O(Target)
4 """
5 total = sum(nums)
6 if total % 2 != 0:
7 return False
8 target = total // 2
9 # dp: dp[w] is True when sum w is reachable using only the items processed so far
10 dp = [False] * (target + 1)
11 dp[0] = True
12 for x in nums:
13 for w in range(target, x - 1, -1):
14 # 1. Use This Item At Most Once:
15 # The reverse loop reads dp[w - x] before this pass updates it, so x is counted at most once per w.
16 dp[w] = dp[w] or dp[w - x]
17 return dp[target]
Invariant: After each item x, dp[w] is True when some chosen items among those processed so far add up to exactly w.

Treat each sum as its own subproblem: dp[w] is True when the items seen so far can add up to w, and each item x updates it from dp[w - x]. Loop w downward, so dp[w - x] has not been touched by the current x yet and x is counted at most once; looping upward would let the same x fill dp[w - x] and then dp[w] in one pass. The trap is exactly that loop direction: for w in range(target, x - 1, -1), not range(x, target + 1).

See it run

Partition Equal Subset Sum, step by step

x - · w - · dp[target] False

x 1 · w - · dp[target] False

x 1 · w 1 · dp[target] False

x 5 · w - · dp[target] False

x 5 · w 6 · dp[target] False

x 5 · w 5 · dp[target] False

x 11 · w - · dp[target] False

x 11 · w 11 · dp[target] True

x 5 · w - · dp[target] True

x 5 · w 11 · dp[target] True

x 5 · w 10 · dp[target] True

x 5 · w 6 · dp[target] True

x 5 · w 5 · dp[target] True

x - · w - · dp[target] True

Row k is the list dp after the k-th item. Column w is the sum w. T means True and F means False.

  • write the cell being written
  • read the cell dp[w - x], read before this item changes it
  • answer the final answer

Start: nums = [1, 5, 11, 5], total = 22, target = 11. dp[0] = True because the sum 0 needs no item. Every other cell is False.

Item x = 1. The loop runs w from 11 down to 1. Only sums where dp[w - 1] is True can change, so only those are shown.

w = 1: dp[0] is True and does not use item 1 yet. So dp[1] becomes True.

Item x = 5. The loop runs w from 11 down to 5. Only sums where dp[w - 5] is True can change, so only those are shown.

w = 6: dp[1] is True and does not use item 5 yet. So dp[6] becomes True.

w = 5: dp[0] is True and does not use item 5 yet. So dp[5] becomes True.

Item x = 11. The loop runs w from 11 down to 11. Only sums where dp[w - 11] is True can change, so only those are shown.

w = 11: dp[0] is True and does not use item 11 yet. So dp[11] becomes True.

Item x = 5. The loop runs w from 11 down to 5. Only sums where dp[w - 5] is True can change, so only those are shown.

w = 11: dp[6] is True, but dp[11] is already True, so it stays True.

w = 10: dp[5] is True and does not use item 5 yet. So dp[10] becomes True.

w = 6: dp[1] is True, but dp[6] is already True, so it stays True.

w = 5: dp[0] is True, but dp[5] is already True, so it stays True.

All items are processed. dp[11] is True, so the array can be split into two equal halves.

start
0Twrite
1F
2F
3F
4F
5F
6F
7F
8F
9F
10F
11F
x = 1
x = 5
x = 11
x = 5
1 / 14
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Looping w upward in a 0/1 problem

    Mistake
    for w in range(x, target + 1):
    Fix
    for w in range(target, x - 1, -1):

    What happens: Going upward, dp[w - x] may already include the current x. Then dp[w] uses x a second time. Here x = 1 makes every sum from 1 to 4 reachable, so the target 4 looks reachable. Going downward, dp[w - x] still describes only the items before x.

    Example: nums = [1, 2, 5]
    Mistake: returns TrueFix: returns False
  2. 2

    Gotcha 2: Skipping the check for an odd total

    Mistake
    target = total // 2
    Fix
    if total % 2 != 0:
        return False
    target = total // 2

    What happens: Two equal halves need an even total. For an odd total, total // 2 rounds down. The search then finds a subset for that smaller target. So it returns True for a split that is impossible.

    Example: nums = [1, 2]
    Mistake: returns TrueFix: returns False
  3. 3

    Gotcha 3: Starting dp with every sum reachable

    Mistake
    dp = [True] * (target + 1)
    Fix
    dp = [False] * (target + 1)

    What happens: dp[w] is True only when some chosen items add up to exactly w. Before any item is processed, only the sum 0 is reachable, so dp[0] is True and every other cell is False. If every cell starts True, every target looks reachable.

    Example: nums = [1, 2, 5]
    Mistake: returns TrueFix: returns False
Not this one if…
  • Each item can be used any number of times, as in Coin Change (LC 322): the capacity loop runs forward, so an item may fill dp[w - item] and then dp[w] in the same pass. See Unbounded Knapsack
  • You must return every combination itself, not just whether a sum is reachable, as in Combination Sum (LC 39): a table of booleans per sum can't list them. See Combinations

Deep Dive

The state is a sum, and the items arrive one at a time

In a 0/1 knapsack problem you choose items, and each item is either taken once or left out. The state is a sum w. In can_partition, dp[w] is True when some of the items processed so far add up to exactly w.

Processing items in order is what makes the table correct. After item k, dp describes only the first k items. The next item x adds one choice: take x once, so dp[w] also becomes True when dp[w - x] was True before x.

At the start, only the sum 0 is reachable, by choosing no item. So dp[0] is True and every other cell is False. If every cell started True, every target would look reachable.

Example: for nums = [1, 5, 11, 5] the target is 11. The reachable sums are [0, 1] after the first item, [0, 1, 5, 6] after the second, [0, 1, 5, 6, 11] after 11, and [0, 1, 5, 6, 10, 11] after the last 5. The target 11 is reachable, so the answer is True.

Why the loop runs downward

Think of a table with one row per item, where dp[i][w] = dp[i - 1][w] or dp[i - 1][w - x]. Both terms read row i - 1. The code compresses the table to one array, so dp[w - x] must still hold the value from before item x.

Looping w downward gives exactly that. When the loop updates dp[w], the smaller index w - x has not changed yet in this pass. So x is used at most once.

Looping w upward does the opposite. dp[w - x] was already updated in this pass, so it may already include x, and dp[w] uses x a second time.

Example: can_partition([2, 6]) has total 8 and target 4, and the answer is False. If the loop runs upward, dp[2] becomes True, then dp[4] reads dp[2] and becomes True. The single 2 was used twice. The downward loop returns False.

Why the total must be even

Two equal halves need an even total. The code checks total % 2 != 0 first and returns False.

Without the check, total // 2 rounds down. For nums = [1, 2] the total is 3, and the rounded target is 1. The subset [1] reaches 1, so the code would return True for a split that does not exist.

The same idea turns other problems into this one. In Last Stone Weight II (LC 1049), splitting the stones into two groups with the smallest weight difference means finding the largest reachable sum that is at most half of the total. For [2, 7, 4, 1, 8, 1] the answer is 1. In Target Sum (LC 494), choosing plus or minus signs turns into counting the subsets that add up to (total + target) / 2. The table then holds counts, and dp[0] starts at 1.

The cost: states times work per state, and why it is pseudo-polynomial

There are N items and target sums, and each update is one comparison. The time is O(N * Target). The space is O(Target) for the single array.

The cost depends on the value of target, not on how many digits it has. A target of 10^9 would need a billion-entry array. This is called pseudo-polynomial time: it is polynomial in the number's value but exponential in the number of bits that write it. So check the constraints first. In Partition Equal Subset Sum (LC 416), the total is at most 20,000.

A second limit can be a pair. In Ones and Zeroes (LC 474), each string uses some zeros and some ones, and two budgets limit the choice. The state becomes the pair (zeros, ones), and both loops run downward. The time is O(N * M * K) for M zeros and K ones. For ["10", "0001", "111001", "1", "0"] with 5 zeros and 3 ones, the answer is 4.

60-Second Pitch

"The brute force tries every subset of the items, which is 2 to the power N subsets. For an equal split, the total must be even, and I look for a subset that adds up to half of it. I define dp[w] as True when some of the items seen so far add up to w. Only dp[0] starts True. Each item x sets dp[w] from dp[w - x]. I loop w downward, so dp[w - x] has not been touched by this item, and the item is used at most once. There are N items and up to total over 2 sums, so the time is O(N * Target). The space is O(Target). The time depends on the value of the target, so I check that the target is small before I choose this method."

Common Questions

Why is the 0/1 knapsack loop downward?

The one-dimensional array holds one row of the table. The update must read the row before the current item. A downward loop reaches the smaller index w - x before it is changed in this pass, so it still holds the old value. An upward loop would read a value that may already include x.

What are the time and space complexity, and is it polynomial?

O(N * Target) time and O(Target) space. It is pseudo-polynomial: it grows with the value of target, not with the number of digits of target. For a target near 10^9, the array would not fit in memory.

How do you recover which items were chosen?

A one-dimensional array loses that information. Keep the two-dimensional table dp[i][w] and walk back from the last row. If dp[i][w] equals dp[i - 1][w], item i was skipped. Otherwise item i was taken, and w decreases by its size. This costs O(N * Target) space.

How would you count the subsets instead of asking whether one exists?

Make dp hold counts, with dp[0] = 1 and dp[w] += dp[w - x] in the same downward loop. This is the step used in Target Sum (LC 494) after the signs are turned into a subset sum.

How would you handle a very large target?

First check whether the numbers have more structure. If the number of items is small, about 40 or fewer, a meet-in-the-middle search can work. It lists the subset sums of each half and combines them. If the target is large but the items are small, the table can sometimes use a bitset, where one bit stands for each reachable sum. Say which constraint you rely on.

SWE Reasoning

Spot it

  • You choose items, and each item is taken at most once. The chosen sizes must add up to a target, or stay within a budget.
  • The question is yes or no, a count of subsets, or the best value: can the target be made, how many subsets, how large a sum.
  • The target is small enough to be an array index, usually below about 20,000.
  • A problem about two equal groups, or the smallest difference between two groups, can be reduced to a subset sum of half the total.
  • The statement says each item is used once. That picks the downward loop.

The invariant, step by step

  1. The total is checked first. If it is odd, the code returns False. Otherwise target = total // 2.
  2. dp[0] is True and every other cell is False. Only the sum 0 is reachable before any item.
  3. The outer loop takes one item x. At this moment dp[w] is correct for all items before it.
  4. The inner loop visits each sum w from target down to x.
  5. The update dp[w] = dp[w] or dp[w - x] reads dp[w - x], which the downward loop has not changed yet in this pass. So x is counted at most once.
  6. When the outer loop ends, dp[w] is True when some chosen items add up to exactly w, which is the card's invariant. The code returns dp[target].

Traps

  • Loop upward. One item is used more than once. can_partition([1, 2, 5]) returns True instead of False. With x = 1, every sum from 1 to 4 looks reachable.
  • Skip the odd-total check. total // 2 rounds down, and the search finds a subset for the smaller target. can_partition([1, 2]) returns True instead of False.
  • Start dp with every sum reachable. If every cell starts True, every target looks reachable. can_partition([1, 2, 5]) returns True instead of False.
  • Forget dp[0] = True. Then no sum is ever reachable, and the answer is always False.
  • Use a huge target. An array of size target + 1 is too large when the target is around 10^9. Check the constraint before choosing this method.

Practice

Canonical LeetCode:Partition Equal Subset Sum (LC 416)Last Stone Weight II (LC 1049)Target Sum (LC 494)Ones and Zeroes (LC 474)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•