Description

給定一個字串 s,將 s 分割成若干子字串,使得每個子字串都是回文。回傳所有可能的分割方式。

Example:

Input: s = “aab” Output: [[“a”,“a”,“b”],[“aa”,“b”]]

Intuition#

核心思路:回溯嘗試所有分割位置,只有當前子字串是回文時才繼續遞迴。

  • 從 index 0 開始,嘗試所有可能的第一段切割(長度 1, 2, 3, …)
  • 只有當切下的子字串是回文時,才遞迴處理剩餘部分
  • 當 index 到達字串尾部,就得到一組合法分割

Approaches#

1: Backtracking + 每次檢查回文#

  • 概念: 遞迴嘗試每個切割點,用雙指標檢查子字串是否為回文
  • 時間複雜度: O(n * 2^n) - 最壞 2^n 種切割,每次檢查回文 O(n)
  • 空間複雜度: O(n) - 遞迴深度
class Solution {
    fun partition(s: String): List<List<String>> {
        val result = mutableListOf<List<String>>()
        val current = mutableListOf<String>()

        fun isPalindrome(left: Int, right: Int): Boolean {
            var l = left; var r = right
            while (l < r) {
                if (s[l] != s[r]) return false
                l++; r--
            }
            return true
        }

        fun backtrack(start: Int) {
            if (start == s.length) {
                result.add(ArrayList(current))
                return
            }

            for (end in start until s.length) {
                if (isPalindrome(start, end)) {
                    current.add(s.substring(start, end + 1))
                    backtrack(end + 1)
                    current.removeAt(current.lastIndex)
                }
            }
        }

        backtrack(0)
        return result
    }
}

⭐2: Backtracking + DP 預處理回文表#

  • 概念: 先用 DP 建立回文判斷表 dp[i][j],回溯時 O(1) 查詢子字串是否為回文
  • 時間複雜度: O(n^2 + 2^n) - 預處理 O(n^2),回溯最壞 O(2^n)
  • 空間複雜度: O(n^2) - 回文表
class Solution {
    fun partition(s: String): List<List<String>> {
        val n = s.length
        val isPalin = Array(n) { BooleanArray(n) }

        // DP 預處理:isPalin[i][j] 表示 s[i..j] 是否為回文
        for (right in 0 until n) {
            for (left in 0..right) {
                if (s[left] == s[right] && (right - left <= 2 || isPalin[left + 1][right - 1])) {
                    isPalin[left][right] = true
                }
            }
        }

        val result = mutableListOf<List<String>>()
        val current = mutableListOf<String>()

        fun backtrack(start: Int) {
            if (start == n) {
                result.add(ArrayList(current))
                return
            }

            for (end in start until n) {
                if (isPalin[start][end]) {
                    current.add(s.substring(start, end + 1))
                    backtrack(end + 1)
                    current.removeAt(current.lastIndex)
                }
            }
        }

        backtrack(0)
        return result
    }
}

🔑 Takeaways#

  • Pattern: 分割問題 = 回溯 + 條件篩選(只在滿足條件時才遞迴)
  • 關鍵技巧: DP 預處理可以將重複的回文判斷從 O(n) 降到 O(1);這種「預處理 + 回溯」的組合在很多題目中都有用