Recursion & Backtracking · Recursion & Backtracking Fundamentals

Backtracking Partition Enumeration

Branching over every place a string could be split, keeping only splits whose current piece satisfies a required property - producing every valid full partition of the string.

  • 5 min read
  • 8 practice questions
  • 3 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: a fixed number of pieces
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

This skill is about splitting a string into pieces that each follow a rule: every piece a palindrome, every piece a dictionary word, every piece a valid IP octet. The task is to list every way of doing it. The pieces must cover the string in order, so the only decision at each step is where the next piece ends. Backtracking over that one decision lists every valid partition. Checking each piece as soon as it's cut throws away doomed splits early.

We'll go from simple to more technical:

  1. The pattern: try every end for the next piece, recurse on the rest, and undo the choice.
  2. A second flavor: a fixed number of pieces (restoring IP addresses).
  3. Where segmentation shows up in production (word splitting, parsing).
  4. What breaks it: late checks, a missing undo, repeated checks, and format rules.

The approach

Java

void backtrack(String s, int start, List<String> current, List<List<String>> result) {
    if (start == s.length()) {
        result.add(new ArrayList<>(current));   // every piece so far passed - a complete partition
        return;
    }
    for (int end = start + 1; end <= s.length(); end++) {
        String piece = s.substring(start, end);
        if (isValid(piece)) {                    // only continue from valid pieces
            current.add(piece);
            backtrack(s, end, current, result);
            current.remove(current.size() - 1);  // un-choose, then try a longer piece
        }
    }
}

Python

def backtrack(s: str, start: int, current: list[str], result: list[list[str]]) -> None:
    if start == len(s):
        result.append(current[:])               # every piece so far passed - a complete partition
        return
    for end in range(start + 1, len(s) + 1):
        piece = s[start:end]
        if is_valid(piece):                     # only continue from valid pieces
            current.append(piece)
            backtrack(s, end, current, result)
            current.pop()                       # un-choose, then try a longer piece

This is the choose-explore-un-choose loop from combinations, with a different choice: instead of "which element next", it's "where does this piece end". Reaching the end of the string means every piece along the way was valid, so the current list is a complete answer. Checking validity at the cut, not at the end, prunes every branch that starts with an invalid piece.

A second flavor: a fixed number of pieces

Restoring IP addresses splits a digit string into exactly four octets, each 0-255 with no leading zeros. A fixed piece count adds a strong prune: with slots pieces left to place, the remaining digits must number between slots and 3 * slots.

Java

if (parts.size() == 4) { if (start == s.length()) result.add(String.join(".", parts)); return; }
int remaining = s.length() - start, slots = 4 - parts.size();
if (remaining < slots || remaining > 3 * slots) return;          // can't possibly fit
for (int len = 1; len <= 3 && start + len <= s.length(); len++) {
    String piece = s.substring(start, start + len);
    if (len > 1 && piece.charAt(0) == '0') break;                 // "0" is fine, "01" is not
    if (Integer.parseInt(piece) > 255) break;
    parts.add(piece);
    build(s, start + len, parts, result);
    parts.remove(parts.size() - 1);
}

Python

def build(s: str, start: int, parts: list[str], result: list[str]) -> None:
    if len(parts) == 4:
        if start == len(s):
            result.append(".".join(parts))
        return
    remaining, slots = len(s) - start, 4 - len(parts)
    if remaining < slots or remaining > 3 * slots:
        return                                   # can't possibly fit
    for length in range(1, 4):
        if start + length > len(s):
            break                                # a slice past the end is just shorter - no error to stop you
        piece = s[start:start + length]
        if length > 1 and piece[0] == "0":
            break                                # "0" is fine, "01" is not
        if int(piece) > 255:
            break
        parts.append(piece)
        build(s, start + length, parts, result)
        parts.pop()

Each piece's rules are checked the moment it's cut. Dropping the leading-zero rule doesn't just add a few answers: on "010010" it produces 10 addresses (such as 01.0.0.10) where only 2 are valid.

Where you'll see it

Tokenizers and input-method editors segment run-together text into dictionary words; parsers for loosely formatted identifiers (IP addresses, dates, product codes) try each split and validate the pieces. When only one best segmentation is needed, production systems switch to dynamic programming - the same "where's the next cut" recursion, memoized.

Which tool for which job?

You needUse
Every valid partitionbacktracking over the next cut, checking each piece
Many palindrome checksa precomputed isPal[i][j] table
A fixed number of piecesprune on the remaining length versus the pieces left
Every segmentation, with dead ends possiblebacktracking plus a "can this suffix finish?" table
Does any partition exist, or the fewest pieces?DP over start positions, no enumeration

Common mistakes

MistakeWhat happensDo this instead
Validating only complete partitionsevery split is explored, even doomed onescheck each piece when it's cut
Not removing the last piecepieces from abandoned branches leak into resultsundo after each recursive call
Re-checking pieces from scratch43,682 comparisons for "a" x 16an isPal table built once
No dead-suffix check in Word Break8,146,016 calls to find 0 answersskip starts whose suffix can't finish
Missing the leading-zero rule"010010" gives 10 addresses instead of 2reject pieces like "01"

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. How many ways can "aaaaaa" be partitioned so that every piece is a palindrome?

Show answer

32 = 2^5. Every substring of an all-same string is a palindrome, so each of the 5 gaps can independently be cut or not.

2. Why prune with "remaining digits must be between slots and 3 * slots" in Restore IP Addresses?

Show answer

Each remaining octet takes 1 to 3 digits, so with slots octets left the remaining digit count must fit that range. Any branch outside it can never produce an address - cutting it immediately avoids exploring pieces that are doomed by length alone.

3. "Can this string be segmented into dictionary words?" - backtracking or DP?

Show answer

DP. Only a yes/no is needed, and "can the suffix starting at i be segmented?" is the same question every time it's reached, so each start position is solved once - O(n^2) substring checks. Backtracking is for when every segmentation must be listed.

Key takeaways

  • Partitioning is choose-explore-un-choose over "where does the next piece end".
  • Validate each piece as it's cut, and record a copy when the string is used up.
  • Precompute expensive piece checks (a palindrome table) instead of repeating them.
  • Fixed piece counts allow length-based pruning (IP addresses: slots to 3 * slots digits left).
  • Up to 2^(n - 1) partitions exist; existence and minimum questions are DP, not enumeration.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Every partitionnext-cut backtracking
Palindrome checksisPal[i][j] table
Fixed piece countremaining-length pruning
Dead ends possiblea "suffix can finish" table
Exists, count or fewestDP

Snippets worth knowing by heart

Java

void go(String s, int start, List<String> cur, List<List<String>> out) {
    if (start == s.length()) { out.add(new ArrayList<>(cur)); return; }
    if (!canFinish[start]) return;                     // dead suffix
    for (int end = start + 1; end <= s.length(); end++)
        if (isPal[start][end]) { cur.add(s.substring(start, end)); go(s, end, cur, out); cur.remove(cur.size() - 1); }
}

Python

cuts = [0] * (n + 1)                     # fewest palindrome pieces, no enumeration
for j in range(1, n + 1):
    cuts[j] = min(cuts[i] + 1 for i in range(j) if is_pal[i][j])

The traps

  • Validating late: checking only complete partitions explores every doomed split. Check each piece as you cut it.
  • No undo: pieces from abandoned branches leak into later results. Remove the last piece after each recursive call.
  • Re-checking pieces: testing every substring for palindrome from scratch repeats work. Build an isPal table once.
  • No dead-suffix check: Word Break makes millions of calls to find nothing. Skip starts whose suffix can't finish.
  • Leading zeros in IP octets: "010010" yields 10 addresses instead of 2. Reject pieces like "01".

Staff-level points, one line each

  • Word Break on "a" × 24 + "b": 8,146,016 calls for 0 answers, and 1 call with the dead-suffix table.
  • "a" × 16 has 32,768 palindrome partitions. Re-checking pieces took 43,682 comparisons; the table took 120 cells, built once.
  • Fewest pieces: 4 by listing all 5,231 partitions, and 4 by an O(n²) DP.
  • Complexity of an enumeration: output size × n, plus dead branches.

Say it in an interview

  • "The only choice is where the next piece ends, and I check each piece when I cut it."
  • "I precompute palindromes, and whether each suffix can finish, so dead branches die at once."
  • "If they only want a count or the minimum, it's a DP."
  • "The output can be 2^(n - 1), so that's the floor on the running time."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 3 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Backtracking Partition Enumeration free

Palindrome Partitioning

MediumProCoding problemJava · Python

Cut a string s into pieces so that every piece reads the same forwards and backwards. List every way to do it.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Restore IP Addresses

MediumProCoding problemJava · Python

An IPv4 address is four numbers from 0 to 255 joined by dots, and a number can't start with 0 unless it is 0 itself (0 is fine, 01 isn't). Given a string of digits, find every IPv4 address you can make by putting three dots into it, without moving or dropping any digit.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Word Break (Print All Ways)

HardProCoding problemJava · Python

Split the string s into words from a dictionary, using every letter exactly once and any dictionary word as often as you like. Return every possible split, each one as its words separated by spaces.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Backtracking Partition Enumeration free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime