bigonotes
DSA: Patterns & PythonBacktracking

String Partitioning

Cut a string into valid pieces by growing a prefix one character at a time and recursing only when the prefix passes the rules.

5 min readLast updated

Many string problems ask you to cut a string into pieces that each follow a rule: every piece a palindrome, the pieces forming a Fibonacci-like sequence, four pieces forming an IP address. The choice at each step is where the next cut goes. Grow the next piece one character at a time, and recurse on the rest of the string only when the piece is valid.

The idea#

From position index, the next piece is s[index:i + 1] for each i from index to the end:

  1. Choices: every possible end i of the next piece.
  2. Hard stop: a piece that breaks a rule. Use continue if a longer piece might still be valid, and break if no longer piece can be.
  3. Goal: index == len(s), meaning the whole string has been cut into valid pieces (plus any count rule, such as "at least 3 numbers").

Why it works: you only recurse after a valid piece, so every path that reaches the end of the string is a valid partition. Every partition is one sequence of cut positions, so the loop over i reaches all of them.

The number of possible cut patterns sets the cost. A string of length N has N − 1 gaps, and each can be cut or not, so there are up to 2N−12^{N-1} ways to partition it. With O(N)O(N) work per partition (checking a piece, copying the path), that's O(N⋅2N)O(N \cdot 2^N).

break vs continue#

The hard stop in step 2 is where most of the pruning happens, and it comes down to break or continue. Ask one question: can a longer piece fix the rule the current piece breaks?

Rule broken by the current piece Can a longer piece fix it? Use
Not a palindrome yes ("ab" fails, "aba" passes) continue
Leading zero ("05") no, every longer piece also starts with 0 break
Number too large (over a limit) no, longer means larger break
Not equal to a required value only if it is still smaller continue while smaller, break once larger

How to spot it#

These cues point to string partitioning.

  • "Partition the string so that every substring is…".
  • "Split the digits into a sequence where…", "restore", "insert separators".
  • Return all partitions, or decide whether one exists.
  • Rules on each piece: palindrome, numeric range, no leading zeros, in a dictionary.

Template#

The template follows the three steps above: grow the next piece, apply the break and continue rules, and recurse only on valid pieces.

track(index):
    if index == length of s:           # whole string cut into valid pieces
        record a copy of path
        return
    for i from index to length of s - 1:
        piece = s[index .. i]
        if piece breaks a rule and longer pieces can't fix it: break
        if piece breaks a rule: continue   # a longer piece may still pass
        append piece to path
        track(i + 1)
        pop from path
track(0)

When one split is enough#

Some problems need only one valid split. Make track return True at the goal and write if track(i + 1): return True in the loop. The search then stops on the first success instead of collecting every split.

Pitfalls#

Most of these come up in the numeric variants, where each piece is read as a number.

  • Missing the leading-zero rule. "0" is valid; "01" is not.
  • An off-by-one bound. The largest signed 32-bit value is 231−12^{31} - 1, so the check is val > 2**31 - 1, not val > 2**31.
  • continue where break is safe. It still gives the right answer, but explores pieces that can never work.
  • Collecting every split when one is enough. Return early on the first success.

Problems in this pattern#

These problems use this pattern. Solve them before you open the hints below. Start with Palindrome Partitioning.

Hints#

Open a hint only after you have tried the problem for a while.

Hint: Palindrome Partitioning

The rule is "the piece is a palindrome". A failed piece may still grow into a palindrome, so use continue, never break. Check with two pointers from both ends of s[index:i + 1].

Hint: Additive Number

Grow each number character by character. Once the path has two numbers, the next must equal their sum: continue while the piece is smaller, break once it is larger. A piece with a leading zero ends the loop. You need only one valid split, and the goal also needs at least 3 numbers.

Hint: Split Array into Fibonacci Sequence

Same rules as Additive Number, plus each number must fit in a signed 32-bit int: break once it exceeds 231−12^{31} - 1. Return the path from the first success, or an empty list. For example, "1101111" splits into [11, 0, 11, 11].

Hint: Restore IP Addresses

You need exactly 4 pieces, each from 0 to 255 with no leading zeros, so pieces are at most 3 characters long. Prune when the characters left can't fill the pieces left (fewer than 1 or more than 3 each).

Hint: Word Break II

The rule is "the piece is in the dictionary". Plain DFS repeats the same suffix many times, so memoize the list of sentences for each start index.

Key takeaways#

  • The choice is where the next cut goes; grow the piece one character at a time.
  • Recurse only when the current piece is valid.
  • continue when a longer piece could still pass, break when it can't.
  • Partitioning a string of length N is O(N⋅2N)O(N \cdot 2^N) in the worst case.
  • Leading zeros and numeric bounds (231−12^{31} - 1, 255) are the classic pruning rules.
Revision notes · 2 min

Core idea#

Cut a string into pieces that each follow a rule. The choice at each step is where the next cut goes. Grow the piece one character at a time, and recurse only when it is valid.

Key points#

  • From index, the next piece is s[index:i + 1] for each i up to the end.
  • Goal: index == len(s), plus any count rule such as "at least 3 numbers".
  • A string of length N has up to 2N−12^{N-1} partitions, so the cost is O(N⋅2N)O(N \cdot 2^N).
  • Use continue when a longer piece might still pass. Use break when no longer piece can.
  • If one valid split is enough, make track return True and stop on the first success.
  • Restore IP Addresses: exactly 4 pieces of 1 to 3 characters, each 0 to 255. Prune when the characters left cannot fill the pieces left.
  • Spot it: "partition so every substring is…", "split the digits into a sequence", "restore", "insert separators".

Break or continue#

Rule broken by the piece Use
Not a palindrome continue ("aba" may pass)
Leading zero ("05") break
Number over 231−12^{31} - 1 or over 255 break
Not the sum of the previous two continue while smaller, break once larger

Template#

track(index):
    if index == length of s: record a copy of path; return
    for i from index to length of s - 1:
        piece = s[index .. i]
        if piece fails and longer pieces can't fix it: break
        if piece fails: continue       # a longer piece may still pass
        append piece to path
        track(i + 1)
        pop from path

Pitfalls#

  • Missing the leading-zero rule. "0" is valid; "01" is not.
  • Checking val > 2**31. The bound is val > 2**31 - 1.
  • Using continue where break is safe. The answer is right, but the search is slower.
  • Collecting every split when one is enough.

Say it in an interview#

  • "Each step chooses where the next cut goes, and recursion happens only on valid pieces."
  • "Leading zeros and size limits allow a break, because longer pieces cannot fix them."

Found a mistake? Email ask@bigonotes.com