Given an integer array, decide whether it can be split into two subsets with equal sums. The key move: reduce to a classic 0/1 subset-sum — can any subset sum to total / 2? A boolean DP set, updated in reverse order, solves it in O(n · target) time.
Given a non-empty array nums of positive integers, return true if it can be partitioned into two subsets with equal sums, false otherwise. For example: nums = [1, 5, 11, 5] → true because [1, 5, 5] sums to 11 and [11] sums to 11. nums = [1, 2, 3, 5] → false.
total / 2. So the problem is equivalent to: "does any subset of nums sum to exactly total / 2?" This is the classic 0/1 subset-sum. We never need to find both halves — if one exists, the other is the remainder.total is odd, two equal halves are impossible. Return false immediately — saves all work.target = total / 2. We only need to check one half.dp = new Set([0]). The value 0 is always reachable (pick nothing).num, snapshot the current dp set, then for every s in the snapshot, add s + num if it is ≤ target. Snapshotting (or iterating high→low on a boolean array) prevents using the same number twice.s + num === target at any point, return true immediately.dp.has(target) after all numbers.Array.from(dp) before the inner loop, or iterate the boolean array from right to left.Using a boolean[target + 1] array instead of a Set gives the same asymptotic space but better cache performance. Either version is accepted; the Set version is easier to reason about. Both are O(target) — not O(n · target) — for space because we only ever keep one row.
22. Even total. Target = 11. Can we pick a subset summing to it?1function canPartition(nums: number[]): boolean {2▶ const total = nums.reduce((a, b) => a + b, 0);3▶ if (total % 2 !== 0) return false; // odd sum — impossible4 const target = total / 2;56 const dp = new Set<number>([0]); // reachable subset sums78 for (const num of nums) {9 const current = Array.from(dp); // snapshot before this round10 for (const s of current) {11 const ns = s + num;12 if (ns === target) return true; // found a valid partition13 if (ns < target) dp.add(ns); // only keep sums ≤ target14 }15 }1617 return dp.has(target);18}
function canPartition(nums: number[]): boolean {
const total = nums.reduce((a, b) => a + b, 0);
if (total % 2 !== 0) return false; // odd sum — impossible
const target = total / 2;
const dp = new Set<number>([0]); // reachable subset sums
for (const num of nums) {
const current = Array.from(dp); // snapshot before this round
for (const s of current) {
const ns = s + num;
if (ns === target) return true; // found a valid partition
if (ns < target) dp.add(ns); // only keep sums ≤ target
}
}
return dp.has(target);
}target = total / 2. The dp set holds every subset-sum reachable so far; it starts as {0} because an empty selection always sums to zero.num we take a snapshot of the current dp set before modifying it. This prevents using num more than once per round (the 0/1 constraint). Then for every existing sum s, candidate s + numis added if it doesn't exceed target. If it equals target we short-circuit immediately.dp.has(target) tells us whether target was ever reachable. At this point the early-exit inside the loop would have already fired if target was hit, so this handles the case where target is only confirmed at the very last step.target = total / 2; O(target) space for the dp set. The outer loop runs n times; the inner snapshot loop is bounded by the number of distinct reachable sums, which cannot exceed target + 1.num and previous sum produced it. After finding target, back-trace to recover one subset.ns < target). Shift everything by the minimum value to make inputs positive, or remove the cap and track the full sum range.boolean[target + 1] array iterated right-to-left is already O(target) — same asymptotically, but avoids Set overhead. This is the textbook 1-D DP table.dp directly without copying) and the solution becomes Coin Change / unbounded knapsack.2ⁿ subsets — O(2ⁿ) time. DP collapses overlapping sub-problems: whether sum s is reachable is computed only once regardless of how many paths lead to it.function canPartition(nums: number[]): boolean {
const total = nums.reduce((a, b) => a + b, 0);
if (total % 2 !== 0) return false; // odd sum — impossible
const target = total / 2;
const dp = new Set<number>([0]); // reachable subset sums
for (const num of nums) {
const current = Array.from(dp); // snapshot before this round
for (const s of current) {
const ns = s + num;
if (ns === target) return true; // found a valid partition
if (ns < target) dp.add(ns); // only keep sums ≤ target
}
}
return dp.has(target);
}target = total / 2; O(target) space for the dp set. The outer loop runs n times; the inner snapshot loop is bounded by the number of distinct reachable sums, which cannot exceed target + 1.Reduce to subset-sum, then enumerate every subset by recursively choosing "include or skip" for each number, asking if any subset hits total / 2. The exhaustive baseline before memoising.
function canPartition(nums: number[]): boolean {
const total = nums.reduce((a, b) => a + b, 0);
if (total % 2 !== 0) return false; // odd sum can't split evenly
const target = total / 2;
// Can the numbers from index i onward reach exactly 'remaining'?
function dfs(i: number, remaining: number): boolean {
if (remaining === 0) return true;
if (remaining < 0 || i === nums.length) return false;
// include nums[i], or skip it
return dfs(i + 1, remaining - nums[i]) || dfs(i + 1, remaining);
}
return dfs(0, target);
}| "partition into two equal subsets" | subset-sum DP to total/2 |
| "can any subset reach value X" | boolean dp set, snapshot per num |
| "0/1 constraint — each item used at most once" | snapshot or right-to-left array |
| "minimize difference between two partitions" | same skeleton, Last Stone Weight II |
function canPartition(nums: number[]): boolean {
const total = nums.reduce((a, b) => a + b, 0);
if (total % 2 !== 0) return false;
const target = total / 2;
const dp = new Set<number>([0]);
for (const num of nums) {
const current = Array.from(dp);
for (const s of current) {
const ns = s + num;
if (ns === target) return true;
if (ns < target) dp.add(ns);
}
}
return dp.has(target);
}total % 2 !== 0 → return false. An odd total can never split into two equal-integer halves.nums = [1, 5, 11, 5], what is target?Array.from(dp) before the inner loop (or iterate right-to-left on a boolean array)?nums = [2, 2, 1]. Does canPartition return true or false?dp contain after initialisation, before any number is processed?