Chapter 20: Backtracking, Constraint Search and State Space
20.04Intermediate5 topics17 min5 interactive animations

Partitioning and sum problems

Sum and partition searches: combination sum, subset sum enumeration, equal-sum partitions, k-partition and matchsticks to a square.

Learning goals

  • Find all combinations that reach a target, with and without reuse
  • Split values into k groups of equal sum
  • Prune with sorted order and overflow tests

Prerequisites

Subset and permutation generation
20.04.1

Combination sum

Find all combinations of candidates that add up to a target, where each candidate may be used any number of times. Sort the candidates, loop from a start index, and recurse with the **same** index (i, not i + 1) because a value may be reused. Stop the loop as soon as sum + a[i] exceeds the target: the candidates are sorted, so every later one overshoots too.

With candidates {2, 3, 5} and target 8 the answers are [2, 2, 2, 2], [2, 3, 3] and [3, 5]. Run the “Combination sum” preset and watch the ✗ nodes where the next candidate overshoots.

Java
1static List<List<Integer>> combinationSum(int[] candidates, int target) {
2 int[] a = candidates.clone(); Arrays.sort(a);
3 List<List<Integer>> out = new ArrayList<>();
4 sumFrom(a, target, 0, 0, new ArrayList<>(), out);
5 return out;
6}
7private static void sumFrom(int[] a, int target, int start, int sum, List<Integer> path, List<List<Integer>> out) {
8 if (sum == target) { out.add(new ArrayList<>(path)); return; }
9 for (int i = start; i < a.length; i++) {
10 if (sum + a[i] > target) break; // sorted: every later value is too big as well
11 path.add(a[i]);
12 sumFrom(a, target, i, sum + a[i], path, out); // i, not i + 1: the same value may be reused
13 path.remove(path.size() - 1);
14 }
15}

Combination sum and subset sum

Interactive · edit the input

Your input

Try

partial solution

[] sum 0

solutions found (0)

none yet
Setup

Find combinations that add up to 8. The candidates are sorted, so as soon as one overshoots the target, all later ones do too and the loop stops (pruning). A value may be chosen again, so the recursion restarts at the same index.

Step 1 / 49
Java
1static void sum(int[] a, int target, int start, int sum, List<Integer> path, List<List<Integer>> out, boolean reuse) { // a sorted, positive
2 if (sum == target) { out.add(new ArrayList<>(path)); return; }
3 for (int i = start; i < a.length; i++) {
4 if (sum + a[i] > target) break; // sorted: every later value is too big as well
5 path.add(a[i]); // choose
6 sum(a, target, reuse ? i : i + 1, sum + a[i], path, out, reuse); // explore (reuse: i again; else: next value)
7 path.remove(path.size() - 1); // unchoose
8 }
9}

Live state

depth

0

solutions

0

No condition evaluated on this step

Call stack (top first)

  1. empty
Execution trace (dry run table)Show 49 steps
StepLineStateWhat happens
1L1depth=0 solutions=0Find combinations that add up to 8. The candidates are sorted, so as soon as one overshoots the target, all later ones do too and the loop stops (pruning). A value may be chosen again, so the recursion restarts at the same index.
2L1depth=1 solutions=0Explore [] sum 0.
3L5depth=1 solutions=0Choose 2.
4L1depth=2 solutions=0Explore [2] sum 2.
5L5depth=2 solutions=0Choose 2.
6L1depth=3 solutions=0Explore [2, 2] sum 4.
7L5depth=3 solutions=0Choose 2.
8L1depth=4 solutions=0Explore [2, 2, 2] sum 6.
9L5depth=4 solutions=0Choose 2.
10L1depth=5 solutions=0Explore [2, 2, 2, 2] sum 8.
11L2depth=5 solutions=1Complete and valid: record [2, 2, 2, 2].
12L7depth=4 solutions=1Unchoose 2: back to [2, 2, 2] sum 6.
13L4depth=5 solutions=1Reject 3: 6 + 3 = 9 is above 8, and the sorted candidates after it are larger still.
14L7depth=3 solutions=1Unchoose 2: back to [2, 2] sum 4.
15L5depth=3 solutions=1Choose 3.
16L1depth=4 solutions=1Explore [2, 2, 3] sum 7.
17L4depth=5 solutions=1Reject 3: 7 + 3 = 10 is above 8, and the sorted candidates after it are larger still.
18L7depth=3 solutions=1Unchoose 3: back to [2, 2] sum 4.
19L4depth=4 solutions=1Reject 5: 4 + 5 = 9 is above 8, and the sorted candidates after it are larger still.
20L7depth=2 solutions=1Unchoose 2: back to [2] sum 2.
21L5depth=2 solutions=1Choose 3.
22L1depth=3 solutions=1Explore [2, 3] sum 5.
23L5depth=3 solutions=1Choose 3.
24L1depth=4 solutions=1Explore [2, 3, 3] sum 8.
25L2depth=4 solutions=2Complete and valid: record [2, 3, 3].
26L7depth=3 solutions=2Unchoose 3: back to [2, 3] sum 5.
27L4depth=4 solutions=2Reject 5: 5 + 5 = 10 is above 8, and the sorted candidates after it are larger still.
28L7depth=2 solutions=2Unchoose 3: back to [2] sum 2.
29L5depth=2 solutions=2Choose 5.
30L1depth=3 solutions=2Explore [2, 5] sum 7.
31L4depth=4 solutions=2Reject 5: 7 + 5 = 12 is above 8, and the sorted candidates after it are larger still.
32L7depth=2 solutions=2Unchoose 5: back to [2] sum 2.
33L7depth=1 solutions=2Unchoose 2: back to [] sum 0.
34L5depth=1 solutions=2Choose 3.
35L1depth=2 solutions=2Explore [3] sum 3.
36L5depth=2 solutions=2Choose 3.
37L1depth=3 solutions=2Explore [3, 3] sum 6.
38L4depth=4 solutions=2Reject 3: 6 + 3 = 9 is above 8, and the sorted candidates after it are larger still.
39L7depth=2 solutions=2Unchoose 3: back to [3] sum 3.
40L5depth=2 solutions=2Choose 5.
41L1depth=3 solutions=2Explore [3, 5] sum 8.
42L2depth=3 solutions=3Complete and valid: record [3, 5].
43L7depth=2 solutions=3Unchoose 5: back to [3] sum 3.
44L7depth=1 solutions=3Unchoose 3: back to [] sum 0.
45L5depth=1 solutions=3Choose 5.
46L1depth=2 solutions=3Explore [5] sum 5.
47L4depth=3 solutions=3Reject 5: 5 + 5 = 10 is above 8, and the sorted candidates after it are larger still.
48L7depth=1 solutions=3Unchoose 5: back to [] sum 0.
49L9solutions=3 nodes=20 return=[[2, 2, 2, 2], [2, 3, 3], [3, 5]]Search finished: 3 solutions, 19 choices tried. Backtracking explores every branch the pruning rules allow.
20.04.2

Subset sum enumeration

The same search with each value usable **once** enumerates the subsets whose sum is the target: recurse with i + 1. This is the exact, exponential version of the subset sum problem; Chapter 23 solves the *decision* version with dynamic programming in O(n · target) time, which is much faster when the target is small. Backtracking is the right tool when you need the actual subsets, not only whether one exists.

The “Subset sum” preset finds [2, 3, 4] and [4, 5] for target 9 from {2, 3, 4, 5}. “No solution” explores and rejects every branch for {4, 6} and target 7.

Java
1static List<List<Integer>> subsetSums(int[] values, int target) { // positive values, each used at most once
2 int[] a = values.clone(); Arrays.sort(a);
3 List<List<Integer>> out = new ArrayList<>();
4 take(a, target, 0, 0, new ArrayList<>(), out);
5 return out;
6}
7private static void take(int[] a, int target, int start, int sum, List<Integer> path, List<List<Integer>> out) {
8 if (sum == target) { out.add(new ArrayList<>(path)); return; }
9 for (int i = start; i < a.length; i++) {
10 if (sum + a[i] > target) break;
11 path.add(a[i]);
12 take(a, target, i + 1, sum + a[i], path, out); // i + 1: each value at most once
13 path.remove(path.size() - 1);
14 }
15}

Combination sum and subset sum

Interactive · edit the input

Your input

Try

partial solution

[] sum 0

solutions found (0)

none yet
Setup

Find combinations that add up to 8. The candidates are sorted, so as soon as one overshoots the target, all later ones do too and the loop stops (pruning). A value may be chosen again, so the recursion restarts at the same index.

Step 1 / 49
Java
1static void sum(int[] a, int target, int start, int sum, List<Integer> path, List<List<Integer>> out, boolean reuse) { // a sorted, positive
2 if (sum == target) { out.add(new ArrayList<>(path)); return; }
3 for (int i = start; i < a.length; i++) {
4 if (sum + a[i] > target) break; // sorted: every later value is too big as well
5 path.add(a[i]); // choose
6 sum(a, target, reuse ? i : i + 1, sum + a[i], path, out, reuse); // explore (reuse: i again; else: next value)
7 path.remove(path.size() - 1); // unchoose
8 }
9}

Live state

depth

0

solutions

0

No condition evaluated on this step

Call stack (top first)

  1. empty
Execution trace (dry run table)Show 49 steps
StepLineStateWhat happens
1L1depth=0 solutions=0Find combinations that add up to 8. The candidates are sorted, so as soon as one overshoots the target, all later ones do too and the loop stops (pruning). A value may be chosen again, so the recursion restarts at the same index.
2L1depth=1 solutions=0Explore [] sum 0.
3L5depth=1 solutions=0Choose 2.
4L1depth=2 solutions=0Explore [2] sum 2.
5L5depth=2 solutions=0Choose 2.
6L1depth=3 solutions=0Explore [2, 2] sum 4.
7L5depth=3 solutions=0Choose 2.
8L1depth=4 solutions=0Explore [2, 2, 2] sum 6.
9L5depth=4 solutions=0Choose 2.
10L1depth=5 solutions=0Explore [2, 2, 2, 2] sum 8.
11L2depth=5 solutions=1Complete and valid: record [2, 2, 2, 2].
12L7depth=4 solutions=1Unchoose 2: back to [2, 2, 2] sum 6.
13L4depth=5 solutions=1Reject 3: 6 + 3 = 9 is above 8, and the sorted candidates after it are larger still.
14L7depth=3 solutions=1Unchoose 2: back to [2, 2] sum 4.
15L5depth=3 solutions=1Choose 3.
16L1depth=4 solutions=1Explore [2, 2, 3] sum 7.
17L4depth=5 solutions=1Reject 3: 7 + 3 = 10 is above 8, and the sorted candidates after it are larger still.
18L7depth=3 solutions=1Unchoose 3: back to [2, 2] sum 4.
19L4depth=4 solutions=1Reject 5: 4 + 5 = 9 is above 8, and the sorted candidates after it are larger still.
20L7depth=2 solutions=1Unchoose 2: back to [2] sum 2.
21L5depth=2 solutions=1Choose 3.
22L1depth=3 solutions=1Explore [2, 3] sum 5.
23L5depth=3 solutions=1Choose 3.
24L1depth=4 solutions=1Explore [2, 3, 3] sum 8.
25L2depth=4 solutions=2Complete and valid: record [2, 3, 3].
26L7depth=3 solutions=2Unchoose 3: back to [2, 3] sum 5.
27L4depth=4 solutions=2Reject 5: 5 + 5 = 10 is above 8, and the sorted candidates after it are larger still.
28L7depth=2 solutions=2Unchoose 3: back to [2] sum 2.
29L5depth=2 solutions=2Choose 5.
30L1depth=3 solutions=2Explore [2, 5] sum 7.
31L4depth=4 solutions=2Reject 5: 7 + 5 = 12 is above 8, and the sorted candidates after it are larger still.
32L7depth=2 solutions=2Unchoose 5: back to [2] sum 2.
33L7depth=1 solutions=2Unchoose 2: back to [] sum 0.
34L5depth=1 solutions=2Choose 3.
35L1depth=2 solutions=2Explore [3] sum 3.
36L5depth=2 solutions=2Choose 3.
37L1depth=3 solutions=2Explore [3, 3] sum 6.
38L4depth=4 solutions=2Reject 3: 6 + 3 = 9 is above 8, and the sorted candidates after it are larger still.
39L7depth=2 solutions=2Unchoose 3: back to [3] sum 3.
40L5depth=2 solutions=2Choose 5.
41L1depth=3 solutions=2Explore [3, 5] sum 8.
42L2depth=3 solutions=3Complete and valid: record [3, 5].
43L7depth=2 solutions=3Unchoose 5: back to [3] sum 3.
44L7depth=1 solutions=3Unchoose 3: back to [] sum 0.
45L5depth=1 solutions=3Choose 5.
46L1depth=2 solutions=3Explore [5] sum 5.
47L4depth=3 solutions=3Reject 5: 5 + 5 = 10 is above 8, and the sorted candidates after it are larger still.
48L7depth=1 solutions=3Unchoose 5: back to [] sum 0.
49L9solutions=3 nodes=20 return=[[2, 2, 2, 2], [2, 3, 3], [3, 5]]Search finished: 3 solutions, 19 choices tried. Backtracking explores every branch the pruning rules allow.
20.04.3

Equal-sum partitions

Can an array be split into two groups with the same sum? The total must be even; the target for each group is half. That is a subset sum question (is there a subset with sum total/2?). For k = 2 the partition search below works, and the dynamic-programming solution of Chapter 23 is faster for large values. In the animation the “Equal halves” preset splits [1, 5, 11, 5] into two groups of 11.

Java
1static boolean canSplitEqually(int[] a) {
2 int total = Arrays.stream(a).sum();
3 if (total % 2 != 0) return false; // an odd total cannot be halved
4 int[] sorted = a.clone(); Arrays.sort(sorted);
5 return reach(sorted, sorted.length - 1, total / 2);
6}
7private static boolean reach(int[] a, int i, int remaining) { // can a[0..i] make exactly `remaining`?
8 if (remaining == 0) return true;
9 if (i < 0 || remaining < 0) return false;
10 return reach(a, i - 1, remaining - a[i]) || reach(a, i - 1, remaining); // take a[i], or skip it
11}

Partition into k equal-sum groups (equal partitions, matchsticks)

Interactive · edit the input

Your input

Try

partial solution

sums [0, 0, 0]

solutions found (0)

none yet

goal 6 per group

0/6G1
0/6G2
0/6G3
Setup

Total 18 split into 3 groups: each group must reach 6. Values are placed largest first so overflowing branches fail early.

Step 1 / 22
Java
1static boolean canPartition(int[] a, int k, boolean symmetry) { // a sorted in descending order
2 int total = Arrays.stream(a).sum();
3 if (total % k != 0 || a[0] > total / k) return false;
4 return place(a, 0, new int[k], total / k, symmetry);
5}
6static boolean place(int[] a, int i, int[] sums, int goal, boolean symmetry) {
7 if (i == a.length) return true; // everything placed
8 for (int b = 0; b < sums.length; b++) {
9 if (sums[b] + a[i] > goal) continue; // this group would overflow
10 if (symmetry && b > 0 && sums[b] == 0 && sums[b - 1] == 0) break; // identical empty groups: try only the first
11 sums[b] += a[i]; // choose
12 if (place(a, i + 1, sums, goal, symmetry)) return true; // explore
13 sums[b] -= a[i]; // unchoose
14 }
15 return false;
16}

Live state

depth

0

solutions

0

No condition evaluated on this step

Call stack (top first)

  1. empty
Execution trace (dry run table)Show 22 steps
StepLineStateWhat happens
1L4depth=0 solutions=0Total 18 split into 3 groups: each group must reach 6. Values are placed largest first so overflowing branches fail early.
2L6depth=1 solutions=0Explore sums [0, 0, 0].
3L11depth=1 solutions=0Choose 4→G1.
4L6depth=2 solutions=0Explore sums [4, 0, 0].
5L9depth=3 solutions=0Reject 4→G1: group 1 would reach 8, above 6.
6L11depth=2 solutions=0Choose 4→G2.
7L6depth=3 solutions=0Explore sums [4, 4, 0].
8L9depth=4 solutions=0Reject 3→G1: group 1 would reach 7, above 6.
9L9depth=4 solutions=0Reject 3→G2: group 2 would reach 7, above 6.
10L11depth=3 solutions=0Choose 3→G3.
11L6depth=4 solutions=0Explore sums [4, 4, 3].
12L9depth=5 solutions=0Reject 3→G1: group 1 would reach 7, above 6.
13L9depth=5 solutions=0Reject 3→G2: group 2 would reach 7, above 6.
14L11depth=4 solutions=0Choose 3→G3.
15L6depth=5 solutions=0Explore sums [4, 4, 6].
16L11depth=5 solutions=0Choose 2→G1.
17L6depth=6 solutions=0Explore sums [6, 4, 6].
18L9depth=7 solutions=0Reject 2→G1: group 1 would reach 8, above 6.
19L11depth=6 solutions=0Choose 2→G2.
20L6depth=7 solutions=0Explore sums [6, 6, 6].
21L7depth=7 solutions=1Complete and valid: record [4→G1, 4→G2, 3→G3, 3→G3, 2→G1, 2→G2].
22L15solutions=1 nodes=13 return=trueSearch finished: 1 solution, 12 choices tried. Backtracking explores every branch the pruning rules allow.
20.04.4

K-partition

Split the values into k groups that all have the same sum. The total must be divisible by k and no value may exceed the target sum total/k. Then place the values one at a time into one of the k groups, rejecting a placement that would push a group above the target. Sorting the values in descending order makes overflow happen early, and the animation's auxiliary row shows each group's running sum against the goal.

Two cheap checks avoid whole searches: a non-divisible total is rejected before any recursion, and a value larger than the goal means failure. For [4, 4, 4, 3, 3] and k = 3 the total 18 is divisible, the goal is 6, but no placement exists; the search needs only 12 nodes with symmetry breaking (see the search-improvements lesson) and 49 without.

Java
1static boolean canPartition(int[] a, int k) { // a sorted in descending order
2 int total = Arrays.stream(a).sum();
3 if (total % k != 0 || a[0] > total / k) return false;
4 return place(a, 0, new int[k], total / k);
5}
6private static boolean place(int[] a, int i, int[] sums, int goal) {
7 if (i == a.length) return true; // everything placed
8 for (int b = 0; b < sums.length; b++) {
9 if (sums[b] + a[i] > goal) continue; // this group would overflow
10 sums[b] += a[i]; // choose
11 if (place(a, i + 1, sums, goal)) return true; // explore
12 sums[b] -= a[i]; // unchoose
13 }
14 return false;
15}

Partition into k equal-sum groups (equal partitions, matchsticks)

Interactive · edit the input

Your input

Try

partial solution

sums [0, 0, 0]

solutions found (0)

none yet

goal 6 per group

0/6G1
0/6G2
0/6G3
Setup

Total 18 split into 3 groups: each group must reach 6. Values are placed largest first so overflowing branches fail early.

Step 1 / 22
Java
1static boolean canPartition(int[] a, int k, boolean symmetry) { // a sorted in descending order
2 int total = Arrays.stream(a).sum();
3 if (total % k != 0 || a[0] > total / k) return false;
4 return place(a, 0, new int[k], total / k, symmetry);
5}
6static boolean place(int[] a, int i, int[] sums, int goal, boolean symmetry) {
7 if (i == a.length) return true; // everything placed
8 for (int b = 0; b < sums.length; b++) {
9 if (sums[b] + a[i] > goal) continue; // this group would overflow
10 if (symmetry && b > 0 && sums[b] == 0 && sums[b - 1] == 0) break; // identical empty groups: try only the first
11 sums[b] += a[i]; // choose
12 if (place(a, i + 1, sums, goal, symmetry)) return true; // explore
13 sums[b] -= a[i]; // unchoose
14 }
15 return false;
16}

Live state

depth

0

solutions

0

No condition evaluated on this step

Call stack (top first)

  1. empty
Execution trace (dry run table)Show 22 steps
StepLineStateWhat happens
1L4depth=0 solutions=0Total 18 split into 3 groups: each group must reach 6. Values are placed largest first so overflowing branches fail early.
2L6depth=1 solutions=0Explore sums [0, 0, 0].
3L11depth=1 solutions=0Choose 4→G1.
4L6depth=2 solutions=0Explore sums [4, 0, 0].
5L9depth=3 solutions=0Reject 4→G1: group 1 would reach 8, above 6.
6L11depth=2 solutions=0Choose 4→G2.
7L6depth=3 solutions=0Explore sums [4, 4, 0].
8L9depth=4 solutions=0Reject 3→G1: group 1 would reach 7, above 6.
9L9depth=4 solutions=0Reject 3→G2: group 2 would reach 7, above 6.
10L11depth=3 solutions=0Choose 3→G3.
11L6depth=4 solutions=0Explore sums [4, 4, 3].
12L9depth=5 solutions=0Reject 3→G1: group 1 would reach 7, above 6.
13L9depth=5 solutions=0Reject 3→G2: group 2 would reach 7, above 6.
14L11depth=4 solutions=0Choose 3→G3.
15L6depth=5 solutions=0Explore sums [4, 4, 6].
16L11depth=5 solutions=0Choose 2→G1.
17L6depth=6 solutions=0Explore sums [6, 4, 6].
18L9depth=7 solutions=0Reject 2→G1: group 1 would reach 8, above 6.
19L11depth=6 solutions=0Choose 2→G2.
20L6depth=7 solutions=0Explore sums [6, 6, 6].
21L7depth=7 solutions=1Complete and valid: record [4→G1, 4→G2, 3→G3, 3→G3, 2→G1, 2→G2].
22L15solutions=1 nodes=13 return=trueSearch finished: 1 solution, 12 choices tried. Backtracking explores every branch the pruning rules allow.
20.04.5

Matchsticks to square

Can all the matchsticks be arranged into the four sides of a square, using every stick exactly once? That is k-partition with k = 4: the sides must all have length total/4. The checks are the same: total divisible by 4, no stick longer than a side. Sort sticks in descending order so long sticks are placed first.

The “Matchsticks to a square” preset uses sticks [1, 1, 2, 2, 2] and finds the sides {2}, {2}, {2}, {1, 1} after 15 nodes: the total is 8, so every side is 2.

Java
1static boolean makesSquare(int[] sticks) {
2 int[] a = sticks.clone(); Arrays.sort(a);
3 for (int i = 0, j = a.length - 1; i < j; i++, j--) { int t = a[i]; a[i] = a[j]; a[j] = t; } // descending
4 if (a.length < 4) return false;
5 int total = Arrays.stream(a).sum();
6 if (total % 4 != 0 || a[0] > total / 4) return false;
7 return fill(a, 0, new int[4], total / 4);
8}
9private static boolean fill(int[] a, int i, int[] sides, int goal) {
10 if (i == a.length) return true;
11 for (int s = 0; s < 4; s++) {
12 if (sides[s] + a[i] > goal) continue;
13 sides[s] += a[i];
14 if (fill(a, i + 1, sides, goal)) return true;
15 sides[s] -= a[i];
16 }
17 return false;
18}

Partition into k equal-sum groups (equal partitions, matchsticks)

Interactive · edit the input

Your input

Try

partial solution

sums [0, 0, 0]

solutions found (0)

none yet

goal 6 per group

0/6G1
0/6G2
0/6G3
Setup

Total 18 split into 3 groups: each group must reach 6. Values are placed largest first so overflowing branches fail early.

Step 1 / 22
Java
1static boolean canPartition(int[] a, int k, boolean symmetry) { // a sorted in descending order
2 int total = Arrays.stream(a).sum();
3 if (total % k != 0 || a[0] > total / k) return false;
4 return place(a, 0, new int[k], total / k, symmetry);
5}
6static boolean place(int[] a, int i, int[] sums, int goal, boolean symmetry) {
7 if (i == a.length) return true; // everything placed
8 for (int b = 0; b < sums.length; b++) {
9 if (sums[b] + a[i] > goal) continue; // this group would overflow
10 if (symmetry && b > 0 && sums[b] == 0 && sums[b - 1] == 0) break; // identical empty groups: try only the first
11 sums[b] += a[i]; // choose
12 if (place(a, i + 1, sums, goal, symmetry)) return true; // explore
13 sums[b] -= a[i]; // unchoose
14 }
15 return false;
16}

Live state

depth

0

solutions

0

No condition evaluated on this step

Call stack (top first)

  1. empty
Execution trace (dry run table)Show 22 steps
StepLineStateWhat happens
1L4depth=0 solutions=0Total 18 split into 3 groups: each group must reach 6. Values are placed largest first so overflowing branches fail early.
2L6depth=1 solutions=0Explore sums [0, 0, 0].
3L11depth=1 solutions=0Choose 4→G1.
4L6depth=2 solutions=0Explore sums [4, 0, 0].
5L9depth=3 solutions=0Reject 4→G1: group 1 would reach 8, above 6.
6L11depth=2 solutions=0Choose 4→G2.
7L6depth=3 solutions=0Explore sums [4, 4, 0].
8L9depth=4 solutions=0Reject 3→G1: group 1 would reach 7, above 6.
9L9depth=4 solutions=0Reject 3→G2: group 2 would reach 7, above 6.
10L11depth=3 solutions=0Choose 3→G3.
11L6depth=4 solutions=0Explore sums [4, 4, 3].
12L9depth=5 solutions=0Reject 3→G1: group 1 would reach 7, above 6.
13L9depth=5 solutions=0Reject 3→G2: group 2 would reach 7, above 6.
14L11depth=4 solutions=0Choose 3→G3.
15L6depth=5 solutions=0Explore sums [4, 4, 6].
16L11depth=5 solutions=0Choose 2→G1.
17L6depth=6 solutions=0Explore sums [6, 4, 6].
18L9depth=7 solutions=0Reject 2→G1: group 1 would reach 8, above 6.
19L11depth=6 solutions=0Choose 2→G2.
20L6depth=7 solutions=0Explore sums [6, 6, 6].
21L7depth=7 solutions=1Complete and valid: record [4→G1, 4→G2, 3→G3, 3→G3, 2→G1, 2→G2].
22L15solutions=1 nodes=13 return=trueSearch finished: 1 solution, 12 choices tried. Backtracking explores every branch the pruning rules allow.

Practice ladder

Now try it yourself

Easy

  • Combination Sum (target 7, candidates 2, 3, 6, 7)
  • Subsets

Medium

  • Combination Sum II
  • Partition Equal Subset Sum
  • Matchsticks to Square

Advanced

  • Partition to K Equal Sum Subsets
  • Target Sum

Key takeaways

  • Sort candidates, restart at i to allow reuse, at i + 1 to forbid it, and break when the sum overshoots.
  • k-partition needs total % k == 0 and max ≤ total/k before any search.
  • Backtracking gives the actual groups; dynamic programming (Chapter 23) answers existence faster.