Skip to content
DSAPrep

    ↑↓ to move · Enter to open · Esc to close

    Palindrome Partitioning

    Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s.

    Example 1

    Input: s = "aab"

    Output: [["a","a","b"],["aa","b"]]

    Example 2

    Input: s = "a"

    Output: [["a"]]

    Constraints

    • 1 <= s.length <= 16
    • s contains only lowercase English letters.
    View original on LeetCode ↗

    At every position in s, decide where the next cut goes: try every prefix of the remaining string, keep it only if that prefix is itself a palindrome, and recurse on what is left. A full partition is found once the whole string has been consumed.

    Brute Force: All Cut Combinations, Filter After

    Time O(2^n · n)Space O(2^n · n)

    A string of length n has n - 1 gaps between characters, each either “cut here” or “no cut” — 2^(n-1) ways total. Generate every combination of cuts, slice the string accordingly, and keep only the partitions where every piece happens to be a palindrome.

    class Solution:
    def partition(self, s: str) -> list[list[str]]:
    n = len(s)
    result = []
    def is_palindrome(sub):
    return sub == sub[::-1]
    def build(cuts):
    pieces = []
    start = 0
    for cut in cuts:
    pieces.append(s[start:cut])
    start = cut
    pieces.append(s[start:])
    if all(is_palindrome(p) for p in pieces):
    result.append(pieces)
    def choose_cuts(gap, chosen):
    if gap == n:
    build(chosen)
    return
    choose_cuts(gap + 1, chosen) # no cut at this gap
    choose_cuts(gap + 1, chosen + [gap]) # cut at this gap
    choose_cuts(1, [])
    return result

    Explores all 2^(n-1) cut patterns regardless of whether an early piece is already not a palindrome — e.g. it still finishes building ["aa","b"]-style attempts even when the first character alone already fails.

    Backtracking: Only Extend Valid Palindromic Prefixes

    OptimalTime O(n · 2^n)Space O(n)

    Instead of choosing all cuts up front, grow the partition one piece at a time: from the current position, try every possible next piece (s[start:end] for each end), and only recurse into it if it is a palindrome. Invalid prefixes are abandoned immediately instead of being carried through to the end.

    class Solution:
    def partition(self, s: str) -> list[list[str]]:
    n = len(s)
    result = []
    path = []
    def is_palindrome(sub):
    return sub == sub[::-1]
    def backtrack(start):
    if start == n:
    result.append(path[:])
    return
    for end in range(start + 1, n + 1):
    piece = s[start:end]
    if is_palindrome(piece):
    path.append(piece)
    backtrack(end)
    path.pop()
    backtrack(0)
    return result

    What to watch: from each start index every prefix is tested, and a prefix that is not a palindrome is rejected on the spot — so the whole subtree beneath it never gets built.

    String

    start = 0

    astart
    a
    b

    Current partition

    empty

    Found partitions

    none yet
    1 / 14
    currentdiscardedseenresult

    Start at index 0 with an empty partition. Try every prefix of aab as the next piece, but only keep the ones that are palindromes.

    Complexity: in the worst case (e.g. all-identical characters like "aaaa...a"), nearly every one of the 2^(n-1) cut patterns is a valid partition, and building each one costs O(n) → O(n · 2^n) time. Checking whether a substring is a palindrome costs O(n) itself in this simple version, so a tighter bound also needs to account for that — a common optimization precomputes palindrome status for all substrings with O(n^2) DP so each check is O(1). Recursion depth and path are bounded by n → O(n) auxiliary space.

    More Backtracking practice