Taro Logo

Palindrome Partitioning

Medium
2 views
2 months ago

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.

Sample Answer
class Solution:
    def partition(self, s: str) -> list[list[str]]:
        res = []
        n = len(s)

        def is_palindrome(sub):
            return sub == sub[::-1]

        def backtrack(start, path):
            if start == n:
                res.append(path[:])  # Append a copy of the current path
                return

            for i in range(start, n):
                sub = s[start:i + 1]
                if is_palindrome(sub):
                    path.append(sub)
                    backtrack(i + 1, path)
                    path.pop()  # Backtrack: remove the last added substring

        backtrack(0, [])
        return res

Naive Approach:

A naive approach would involve generating all possible partitions of the string s and then checking if each partition consists of palindromic substrings. This method is highly inefficient because it considers many invalid partitions.

Optimal Approach:

The optimal approach uses backtracking. It builds partitions recursively, checking at each step if the current substring is a palindrome. If it is, the substring is added to the current partition, and the algorithm recurses on the remaining part of the string. This avoids generating and checking invalid partitions.

Example:

For s = "aab":

  1. Start with an empty path [].
  2. i = 0: sub = "a" is a palindrome. Add it to the path: ["a"]. Recurse with start = 1.
  3. i = 1: sub = "a" is a palindrome. Add it to the path: ["a", "a"]. Recurse with start = 2.
  4. i = 2: sub = "b" is a palindrome. Add it to the path: ["a", "a", "b"]. Recurse with start = 3. start == n. Append the current path to res: res = [["a", "a", "b"]].
  5. Backtrack: Remove "b" from the path: ["a", "a"].
  6. Backtrack: Remove "a" from the path: ["a"].
  7. i = 1: sub = "aa" is a palindrome. Add it to the path: ["a", "aa"]. Recurse with start = 2.
  8. i = 2: sub = "b" is a palindrome. Add it to the path: ["a", "aa", "b"]. Recurse with start = 3. start == n. Append the current path to res: res = [["a", "a", "b"], [["a", "aa", "b"]]].
  9. Backtrack: Remove "b" from the path: ["a", "aa"].
  10. Backtrack: Remove "aa" from the path: ["a"].
  11. Backtrack: Remove "a" from the path: [].
  12. i = 1: sub = "aab" is not a palindrome.
  13. The function returns res.

Big(O) Runtime Analysis:

  • The backtracking function explores all possible partitions of the string s. In the worst case, the number of possible partitions can be exponential, specifically O(2n-1), where n is the length of the string.
  • For each partition, we check if the substring is a palindrome, which takes O(k) time, where k is the length of the substring. In the worst-case scenario, this check might be performed for each possible substring within each partition.
  • Therefore, the overall time complexity is approximately O(N * 2^N), where N is the length of the input string s.

Big(O) Space Usage Analysis:

  • The space complexity is determined by the depth of the recursion and the space required to store the result.
  • In the worst-case scenario, where all characters are the same (e.g., "aaaa"), the recursion depth can be up to n, where n is the length of the string s.
  • Additionally, we need to store the resulting partitions in the res list. In the worst-case scenario, where each character is a palindrome, the number of partitions can be exponential, specifically O(2n-1).
  • Each partition can have a maximum length of n, and we store copies of the substrings. Therefore, the space required to store the result is also approximately O(N * 2^N), where N is the length of the input string s.
  • Therefore, the overall space complexity is approximately O(N * 2^N), where N is the length of the input string s.

Edge Cases:

  1. Empty String: If the input string s is empty, the function should return an empty list [[]]. This is because an empty string is considered a palindrome.
  2. Single Character String: If the input string s contains only one character, the function should return a list containing a list with the single character, e.g., ["a"] for s = "a".
  3. String with No Palindromic Partitions: If the input string s has no palindromic partitions (e.g., s = "abc"), the function should return a list of lists containing individual characters. For example, ["a", "b", "c"].
  4. Long String: The algorithm's time complexity is exponential, so very long strings may cause timeouts. The constraint 1 <= s.length <= 16 mitigates this.
  5. String with All Same Characters: If the string consists of all identical characters, it has many palindromic partitions. The algorithm should correctly generate all of them. For example, for s = "aaa", the output should be [["a", "a", "a"], ["a", "aa"], ["aa", "a"], ["aaa"]].