8

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.

Personally I think, the time complexity is O(n^n), n is the length of the given string.

Thank you Dan Roche, the tight time complexity = O(n* (2^n)), check details below.

#include <vector>
using namespace std;

class Solution {
public:
vector<vector<string>> partition(string s) {
    vector<vector<string>> list;
    vector<string> subList;

    // Input validation.
    if (s.length() <= 1) {
        subList.push_back(s);
        list.push_back(subList);
        return list;
    }

    int len = s.length();
    vector<vector<bool>> memo(len, vector<bool>(len));
    for (int i = 0; i < len; i ++) {
        for (int j = 0; j < len; j ++) {
            if (i >= j) memo[i][j] = true;
            else memo[i][j] = false;
        }
    }

    int start = 0;
    helper(s, start, list, subList, memo);

    return list;
}

void helper(string s, int start, 
            vector<vector<string>> &list, vector<string> &subList,
            vector<vector<bool>> &memo) {

    // Base case.
    if (start > s.length() - 1) {
        vector<string> one_rest(subList);
        list.push_back(one_rest);
        return;
    }

    for (int len = 1; start + len <= s.length(); len ++) {
        int end = start + len - 1;

        memo[start][end] = (len == 1) ||
                           (memo[start + 1][end - 1] && s[start] == s[end]);

        if (memo[start][end] == true) {
            // Have a try.
            subList.push_back(s.substr(start, len));

            // Do recursion.
            helper(s, end + 1, list, subList, memo);

            // Roll back.
            subList.pop_back();
        }
    }
}
};
Zhaonan
  • 939
  • 1
  • 11
  • 20
  • The algo in `partition` looks like O(n^2) to me, but I'm unsure what to make of your `helper` as it's recursive and I haven't dry-run it in my head. – Dai Jul 06 '14 at 00:19
  • Thank you Dai. Since there is a loop in one recursion, which is also hard to me to run it in my head. – Zhaonan Jul 06 '14 at 01:13
  • I have an O(n^2) solution If you want i can post solution. – akashchandrakar Jan 17 '15 at 11:22

2 Answers2

21

Should be O(n*2^n). You are basically trying out every possible partition out there. For a string with length n, you will have 2^(n - 1) ways to partition it. This is because, a partition is equivalent of putting a "|" in b/t two chars. There are n - 1 such slots to place a "|". There are only two choice for each slot - placing a "|" or not placing a "|". Thus 2^(n - 1) ways to place "|"s.

Then for each unique partitioning, you have to traverse the entire string (in the worst case when you have repeating chars) to make sure every partition is a palindrome. so n * 2 ^ (n - 1) = O(n*2^n).

scabbage
  • 1,412
  • 2
  • 15
  • 27
  • I think that the exponent is off by 1. In a string like `aaa`, you can place the `|` (separator) in 3 places not 2, that is, `a|aa`, `aa|a`, `aaa|` – heretoinfinity Aug 08 '21 at 16:19
5

The worst-case running time is O(n * 2^n). This is of course exponential as you suspected, but not as bad as O(n^n).

Here's how I got O(n * 2^n): Your top-level function has an O(n^2) loop to initialize memo, and then a call to helper on the entire string. So if we write H(n) for the cost of calling helper with (s.length()-start) equal to n, then the total cost of your algorithm will be

cost = H(n) + O(n^2)

The base case for H(n) is when s.length() - start equals 1, and then it's just the cost of copying the list:

H(1) = O(n)

And for the recursive case, if the if condition memo[start][end] is true every time, there will be (n-1) recursive calls on size (n-1), (n-2), (n-3), ..., 2, 1. In addition to these recursive calls to helper, you also have to call the substr function on the same sizes, which costs O(n^2) in total. So overall the cost of H(n), for n>1, is

H(n) = H(n-1) + H(n-2) + ... + H(1) + O(n^2)

(I would write that as a summation but SO doesn't have LaTeX support.)

Now you can write the same expression for H(n-1), then substitute back to simplify:

H(n) = 2 H(n-1) + O(n)

And this solves to

H(n) = O(n * 2^n)

Since that is larger than O(n^2), the whole cost is also O(n * 2^n).


Note: You could slightly improve this by pre-computing all the substrings up front, in a single O(n^3) loop. You might as well do the same for the memo array. However, this doesn't change the asymptotic big-O bound.

In fact, O(n * 2^n) is optimal, because in the worst case the string is a repetition of the same character n times, like "aaaaaa", in which case there are 2^n possible partitions, each having size n, for a total output size of Ω(n * 2^n).

Dan R
  • 1,412
  • 11
  • 21
  • 1
    How did you arrive at `H(n) = 2*H(n-1) + O(n)`? Is it that (a) `H(n-1) = H(n-2) + H(n-3) + ... + H(1) + O((n-1)^2)`; (b) `O(n^2) = O(n) + O((n-1)^2)` and therefore `H(n) = H(n-1) + [ H(n-2) + ... + H(1) + O((n-1)^2) ] + O(n) = 2*H(n-1) + O(n)`? Is it correct? I am not sure whether (b) is legit or not. – Adama May 28 '18 at 21:54
  • @Adama You are correct there is something slightly "loose" in my reasoning concerning (b). The key is that the two big-O's that are being subtracted there are really the same function. To be more pedantic, we should define a function g(n) = the time to call `substr` on sizes (n-1), (n-2), ..., 1. Then we have `H(n) = H(n-1) + ... + H(1) + g(n)`, which you can now simplify to `H(n) = 2*H(n-1) + g(n) - g(n-1)`. Finally, you say that the difference `g(n) - g(n-1)` is the time to call `substr` on size `(n-1)`, which is `O(n)` as needed. – Dan R Jul 04 '18 at 14:38