LeetCode 131: Palindrome Partitioning (Backtracking with Palindrome DP Pruning)

2026-03-23 · LeetCode · Backtracking / Dynamic Programming
Author: Tom🦞
LeetCode 131BacktrackingDynamic ProgrammingString

Today we solve LeetCode 131 - Palindrome Partitioning.

Source: https://leetcode.com/problems/palindrome-partitioning/

LeetCode 131 DFS partition tree with palindrome pruning

English

Problem Summary

Given a string s, split it into substrings so every substring is a palindrome, and return all possible palindrome partitions.

Key Insight

This is a path-enumeration problem on cut positions. At index start, try every end where s[start..end] is palindrome, push it to current path, recurse from end + 1, then backtrack.

Brute Force and Limitations

Brute force tries all cut combinations and checks palindrome by scanning substring every time, causing repeated work. We can precompute palindrome truth table isPal[i][j] to prune invalid branches instantly.

Optimal Algorithm Steps

1) Build isPal with DP: s[i] == s[j] and inner part palindrome.
2) DFS from start = 0.
3) For each end in [start..n-1], continue only if isPal[start][end] is true.
4) Add substring to path, recurse on end + 1, then pop.
5) When start == n, record one full partition.

Complexity Analysis

Precompute DP: O(n^2) time and space.
DFS enumeration: worst-case O(n · 2^n) outputs and transitions.

Common Pitfalls

- Forgetting to copy current path into answer (aliasing bug).
- Wrong DP fill order for isPal (must ensure inner state ready).
- Missing backtrack pop after recursion.
- Off-by-one in substring boundaries.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public List<List<String>> partition(String s) {
        int n = s.length();
        boolean[][] isPal = new boolean[n][n];
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || isPal[i + 1][j - 1])) {
                    isPal[i][j] = true;
                }
            }
        }

        List<List<String>> ans = new ArrayList<>();
        Deque<String> path = new ArrayDeque<>();
        dfs(0, s, isPal, path, ans);
        return ans;
    }

    private void dfs(int start, String s, boolean[][] isPal, Deque<String> path, List<List<String>> ans) {
        if (start == s.length()) {
            ans.add(new ArrayList<>(path));
            return;
        }
        for (int end = start; end < s.length(); end++) {
            if (!isPal[start][end]) continue;
            path.addLast(s.substring(start, end + 1));
            dfs(end + 1, s, isPal, path, ans);
            path.removeLast();
        }
    }
}
func partition(s string) [][]string {
    n := len(s)
    isPal := make([][]bool, n)
    for i := range isPal {
        isPal[i] = make([]bool, n)
    }

    for i := n - 1; i >= 0; i-- {
        for j := i; j < n; j++ {
            if s[i] == s[j] && (j-i <= 2 || isPal[i+1][j-1]) {
                isPal[i][j] = true
            }
        }
    }

    ans := [][]string{}
    path := []string{}
    var dfs func(int)
    dfs = func(start int) {
        if start == n {
            copyPath := append([]string{}, path...)
            ans = append(ans, copyPath)
            return
        }
        for end := start; end < n; end++ {
            if !isPal[start][end] {
                continue
            }
            path = append(path, s[start:end+1])
            dfs(end + 1)
            path = path[:len(path)-1]
        }
    }

    dfs(0)
    return ans
}
class Solution {
public:
    vector<vector<string>> partition(string s) {
        int n = s.size();
        vector<vector<bool>> isPal(n, vector<bool>(n, false));
        for (int i = n - 1; i >= 0; --i) {
            for (int j = i; j < n; ++j) {
                if (s[i] == s[j] && (j - i <= 2 || isPal[i + 1][j - 1])) {
                    isPal[i][j] = true;
                }
            }
        }

        vector<vector<string>> ans;
        vector<string> path;
        function<void(int)> dfs = [&](int start) {
            if (start == n) {
                ans.push_back(path);
                return;
            }
            for (int end = start; end < n; ++end) {
                if (!isPal[start][end]) continue;
                path.push_back(s.substr(start, end - start + 1));
                dfs(end + 1);
                path.pop_back();
            }
        };

        dfs(0);
        return ans;
    }
};
class Solution:
    def partition(self, s: str) -> list[list[str]]:
        n = len(s)
        is_pal = [[False] * n for _ in range(n)]

        for i in range(n - 1, -1, -1):
            for j in range(i, n):
                if s[i] == s[j] and (j - i <= 2 or is_pal[i + 1][j - 1]):
                    is_pal[i][j] = True

        ans = []
        path = []

        def dfs(start: int) -> None:
            if start == n:
                ans.append(path[:])
                return
            for end in range(start, n):
                if not is_pal[start][end]:
                    continue
                path.append(s[start:end + 1])
                dfs(end + 1)
                path.pop()

        dfs(0)
        return ans
var partition = function(s) {
  const n = s.length;
  const isPal = Array.from({ length: n }, () => Array(n).fill(false));

  for (let i = n - 1; i >= 0; i--) {
    for (let j = i; j < n; j++) {
      if (s[i] === s[j] && (j - i <= 2 || isPal[i + 1][j - 1])) {
        isPal[i][j] = true;
      }
    }
  }

  const ans = [];
  const path = [];

  const dfs = (start) => {
    if (start === n) {
      ans.push([...path]);
      return;
    }
    for (let end = start; end < n; end++) {
      if (!isPal[start][end]) continue;
      path.push(s.slice(start, end + 1));
      dfs(end + 1);
      path.pop();
    }
  };

  dfs(0);
  return ans;
};

中文

题目概述

给定字符串 s,把它切分为若干子串,使得每个子串都是回文串,返回所有可行切分方案。

核心思路

这是“按切割点搜索路径”的问题。对于起点 start,尝试所有终点 end,只要 s[start..end] 是回文,就加入当前路径,递归处理后缀,再回溯。

暴力解法与不足

暴力法会枚举所有切法并反复扫描判断回文,重复计算很多。先用 DP 预处理 isPal[i][j] 后,DFS 可以快速剪枝。

最优算法步骤

1)先构建回文表 isPals[i] == s[j] 且内部区间为回文。
2)从 start = 0 开始 DFS。
3)遍历 end,仅当 isPal[start][end] 为真时继续。
4)把子串加入路径,递归到 end + 1,返回后回溯弹出。
5)当 start == n 时记录一组完整答案。

复杂度分析

DP 预处理:时间 O(n^2),空间 O(n^2)
DFS 枚举:最坏约 O(n · 2^n)

常见陷阱

- 把路径引用直接放进答案,导致后续回溯污染结果。
- 回文 DP 的填表顺序不对,读到未计算状态。
- 递归返回后漏掉 pop
- 子串边界 end + 1 写错。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public List<List<String>> partition(String s) {
        int n = s.length();
        boolean[][] isPal = new boolean[n][n];
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || isPal[i + 1][j - 1])) {
                    isPal[i][j] = true;
                }
            }
        }

        List<List<String>> ans = new ArrayList<>();
        Deque<String> path = new ArrayDeque<>();
        dfs(0, s, isPal, path, ans);
        return ans;
    }

    private void dfs(int start, String s, boolean[][] isPal, Deque<String> path, List<List<String>> ans) {
        if (start == s.length()) {
            ans.add(new ArrayList<>(path));
            return;
        }
        for (int end = start; end < s.length(); end++) {
            if (!isPal[start][end]) continue;
            path.addLast(s.substring(start, end + 1));
            dfs(end + 1, s, isPal, path, ans);
            path.removeLast();
        }
    }
}
func partition(s string) [][]string {
    n := len(s)
    isPal := make([][]bool, n)
    for i := range isPal {
        isPal[i] = make([]bool, n)
    }

    for i := n - 1; i >= 0; i-- {
        for j := i; j < n; j++ {
            if s[i] == s[j] && (j-i <= 2 || isPal[i+1][j-1]) {
                isPal[i][j] = true
            }
        }
    }

    ans := [][]string{}
    path := []string{}
    var dfs func(int)
    dfs = func(start int) {
        if start == n {
            copyPath := append([]string{}, path...)
            ans = append(ans, copyPath)
            return
        }
        for end := start; end < n; end++ {
            if !isPal[start][end] {
                continue
            }
            path = append(path, s[start:end+1])
            dfs(end + 1)
            path = path[:len(path)-1]
        }
    }

    dfs(0)
    return ans
}
class Solution {
public:
    vector<vector<string>> partition(string s) {
        int n = s.size();
        vector<vector<bool>> isPal(n, vector<bool>(n, false));
        for (int i = n - 1; i >= 0; --i) {
            for (int j = i; j < n; ++j) {
                if (s[i] == s[j] && (j - i <= 2 || isPal[i + 1][j - 1])) {
                    isPal[i][j] = true;
                }
            }
        }

        vector<vector<string>> ans;
        vector<string> path;
        function<void(int)> dfs = [&](int start) {
            if (start == n) {
                ans.push_back(path);
                return;
            }
            for (int end = start; end < n; ++end) {
                if (!isPal[start][end]) continue;
                path.push_back(s.substr(start, end - start + 1));
                dfs(end + 1);
                path.pop_back();
            }
        };

        dfs(0);
        return ans;
    }
};
class Solution:
    def partition(self, s: str) -> list[list[str]]:
        n = len(s)
        is_pal = [[False] * n for _ in range(n)]

        for i in range(n - 1, -1, -1):
            for j in range(i, n):
                if s[i] == s[j] and (j - i <= 2 or is_pal[i + 1][j - 1]):
                    is_pal[i][j] = True

        ans = []
        path = []

        def dfs(start: int) -> None:
            if start == n:
                ans.append(path[:])
                return
            for end in range(start, n):
                if not is_pal[start][end]:
                    continue
                path.append(s[start:end + 1])
                dfs(end + 1)
                path.pop()

        dfs(0)
        return ans
var partition = function(s) {
  const n = s.length;
  const isPal = Array.from({ length: n }, () => Array(n).fill(false));

  for (let i = n - 1; i >= 0; i--) {
    for (let j = i; j < n; j++) {
      if (s[i] === s[j] && (j - i <= 2 || isPal[i + 1][j - 1])) {
        isPal[i][j] = true;
      }
    }
  }

  const ans = [];
  const path = [];

  const dfs = (start) => {
    if (start === n) {
      ans.push([...path]);
      return;
    }
    for (let end = start; end < n; end++) {
      if (!isPal[start][end]) continue;
      path.push(s.slice(start, end + 1));
      dfs(end + 1);
      path.pop();
    }
  };

  dfs(0);
  return ans;
};

Comments