Taro Logo

Palindrome Partitioning

Medium
a month ago

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

For example: Input: s = "aab" Output: [["a","a","b"],["aa","b"]]

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 isPalindrome(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 isPalindrome(sub):
                    path.append(sub)
                    backtrack(i+1, path)
                    path.pop()
        
        backtrack(0, [])
        return res

Explanation:

  1. partition(self, s: str) -> list[list[str]]: This is the main function that takes a string s as input and returns a list of lists of strings, where each inner list represents a palindrome partitioning of s.

  2. res = []: Initializes an empty list res to store the resulting palindrome partitions.

  3. n = len(s): Gets the length of the input string s and stores it in n.

  4. isPalindrome(sub): A helper function to check if a given substring sub is a palindrome. It returns True if sub is a palindrome, and False otherwise. It achieves this by comparing the substring with its reverse.

  5. backtrack(start, path): A recursive function that performs backtracking to find all possible palindrome partitions.

    • start: The starting index of the current substring being considered.
    • path: A list representing the current palindrome partition being built.

    Base Case:

    • if start == n:: If start reaches the end of the string (i.e., start == n), it means a valid palindrome partition has been found. A copy of the path is appended to res. It's important to append a copy path[:] instead of path directly because path is modified during the backtracking process. Appending path directly would mean that all lists in res would eventually point to the same (empty) list.

    Recursive Step:

    • for i in range(start, n):: Iterates through the string from the current start index to the end of the string.
    • sub = s[start:i+1]: Extracts a substring sub from s starting at start and ending at i+1.
    • if isPalindrome(sub):: Checks if the extracted substring sub is a palindrome using the isPalindrome helper function.
      • If sub is a palindrome:
        • path.append(sub): Appends the palindrome sub to the current path.
        • backtrack(i+1, path): Recursively calls the backtrack function with the updated start index (i+1) and the updated path. This continues the partitioning process from the next character after the current palindrome.
        • path.pop(): After the recursive call returns, the last added palindrome sub is removed from the path. This is the backtracking step, which allows exploring other possible partitions.
  6. backtrack(0, []): Initiates the backtracking process by calling the backtrack function with an initial start index of 0 and an empty path.

  7. return res: Returns the list res containing all the palindrome partitions.

Brute Force Solution

The brute force solution would involve generating all possible substrings and checking if each substring is a palindrome. Then, recursively explore the remaining string after each palindrome substring. This approach has a high time complexity because it explores all possible substrings, even those that are not palindromes.

Time Complexity

The time complexity is difficult to precisely determine due to the nature of the backtracking algorithm. However, we can make some observations.

  • In the worst case, where all substrings are palindromes (e.g., "aaaa"), the algorithm explores all possible partitions. The number of possible partitions can be exponential, leading to a time complexity of O(2^n) in the worst case. Each possible partition may take O(n) time to construct. The isPalindrome can take O(n) time. The copying of path takes O(n) time. So each complete path takes O(n) to compute.
  • The isPalindrome check takes O(n) time in the worst case.

Therefore, a reasonable estimate of the worst-case time complexity is O(n * 2^n).

Space Complexity

  • The space complexity is mainly determined by the depth of the recursion and the space used to store the res list and the path list.
  • The maximum depth of the recursion is n (the length of the string).
  • The path list stores at most n strings.
  • In the worst case, the res list can store an exponential number of partitions (O(2^n)). Each partition can have at most n strings.

Therefore, the space complexity is O(n * 2^n) in the worst case, mainly due to the space required to store the results.

Edge Cases

  1. Empty String: If the input string s is empty, the function should return a list containing an empty list. The code handles this case correctly because the loop for i in range(start, n) will not execute when n is 0.

  2. Single Character String: If the input string s has only one character, the function should return a list containing a list with that single character. The code handles this correctly because the isPalindrome function will return true and `[[