~25 minutes · Day 65 · dynamic programming
Tonight's interview shape is Partition Equal Subset Sum: given positive integers, decide whether they can be split into two groups with the same total. The trick is not to build the two groups directly. The trick is to ask whether one group can hit the exact half-sum.
Ask “can some subset hit this exact target?” where target = totalSum/2; each item is usable once, so sweep targets from high to low.
That sentence is the pattern. If the total sum is odd, there is no equal split. If the total sum is even, the whole problem becomes one yes/no question: can some subset make totalSum / 2?
The brute force is to try every subset: take or skip each number, then check whether one path reaches the target. That is O(2^n) time because every item doubles the number of possibilities. It is correct, but it grows too fast for interview input sizes.
def brute(nums, i, target):
if target == 0: return True
if i == len(nums) or target < 0: return False
return brute(nums, i + 1, target - nums[i]) or brute(nums, i + 1, target)
Dynamic programming keeps the useful part of that recursion: the reachable sums. Instead of asking the same “can I make sum s?” question again and again, store each answer in a boolean array.
Use the four DP questions from the training plan:
dp[s] means some processed subset can make sum s.x, sum s is reachable if it already was, or if s - x was reachable.dp[0] = True, because the empty subset makes zero.dp[target].def canPartition(nums):
total = sum(nums) # Python's built-in sum
if total % 2 == 1:
return False # odd total cannot split evenly
target = total // 2
dp = [False] * (target + 1)
dp[0] = True # empty subset makes 0
for x in nums:
for s in range(target, x - 1, -1): # high to low: use x once
dp[s] = dp[s] or dp[s - x]
return dp[target]
sum gives the total, and range can count downward with a negative step. The high-to-low sweep is the part to protect. If you sweep upward, dp[s - x] might have been created by the same x, which accidentally turns a 0/1 choice into an unlimited-use choice.
x = 5, target 11 can be reached if target 6 was reachable before this item.Suppose the question is smaller: with nums = [2, 3, 7, 8], can some subset hit target 10? Before opening the reveal, say the state, transition, base case, and answer location out loud.
The same 0/1 knapsack state works: dp[s] means a processed subset can make s. Start with dp[0] = True. For each number, sweep s from 10 down to that number. The answer is dp[10], which becomes true because 2 + 8 = 10.
| Approach | Time | Space |
|---|---|---|
| Brute force (try every subset) | O(2^n) | O(n) |
| 0/1 knapsack DP (reachable sums) | O(n * target) | O(target) |
The trade is memory for repeated work. The boolean row remembers which sums are reachable, so each item updates each target once.
1. What does dp[s] mean after processing some items?
dp[s] is a yes/no memory cell: after the items processed so far, at least one subset can make exactly s.2. Why sweep target from high to low?
dp[s - x] still refers to the state before using x. That is what keeps the item usable once.3. Spot the bug in this update:
for x in nums:
for s in range(x, target + 1):
dp[s] = dp[s] or dp[s - x]
4. Which prompt is this pattern in disguise?
Solve this on LeetCode, logging it in your Notion tracker with the cycle — predict → attempt → compare → encode:
For the encode step, write four short sentences: state, transition, base case, answer location. That is the DP self-check gate for this lesson.
Watch NeetCode — Partition Equal Subset Sum after your attempt, as the compare step. Find the exact sentence where his reasoning diverges from yours.