【LeetCode】131. Palindrome Partitioning(C++)


Source of the subject: https://leetcode-cn.com/problems/next-greater-element-ii/

Title description

Given a string s, split s into some substrings so that each substring is a palindrome.

Return all possible splitting schemes for s.

Input: "aab"
Output:
[
["aa","b"],
["a","a","b"]
]

General idea

  • At first glance, you may think that it has something to do with dp. Dynamic programming is one of the common ways to deal with sub-problems. The problem is divided into several sub-problems and then solved.
  • Think about it, suppose that when a certain substring is already a palindrome, if the substring is expanded at this time, the expanded substring is a palindrome. The necessary and sufficient condition is that the substring is a palindrome and the substring is two or more substrings. Endpoint characters are equal (there is no need to re-compare palindrome strings with double pointers, because the atomic strings are already satisfied)
  • The state equation is as follows. When i≥j, it is True. In fact, the situation of i>j can be ignored here. From the smallest sub-problem, a single character (s[0] or s[s.length()-1] is both Yes) The formed string is a palindrome and begins processing, and then gradually expands to F(0,s.length())
    F (i, j) = {T ruei ≥ j F (i + 1, j − 1) & & (s [i] = = s [j]) i <j F(i,j)= \left \{ \begin{array} {c}True{\quad}i≥j \\F(i + 1 , j-1)\&\&(s[i]==s[j]){\quad}i<j \end{array} \right.F(i,j)={ TrueijF(i+1,j1)&&(s[i]==s[j])i<j

Introduction to Backtracking

  • Move to other solutions

  • https://leetcode-cn.com/problems/palindrome-partitioning/solution/hui-su-fa-si-lu-yu-mo-ban-by-fuxuemingzh-azhz/

  • Backtracking is an algorithmic idea, and recursion is a programming method. Backtracking can be implemented by recursion.

  • The overall idea of ​​the backtracking method is: search every path, and each backtracking is for a specific path. To search the unexplored area under the current search path, there may be two situations:

  • If the current unsearched area meets the end conditions, the current path is saved and the current search is exited; if the
    current unsearched area needs to continue searching, all current possible choices are traversed: if the choice meets the requirements, the current choice is added to the current search path, And continue to search for new unexplored areas.
    The unsearched area mentioned above refers to the unsearched area when searching for a certain route, not the global unsearched area.
    Insert picture description here

Backtracking + dynamic programming

  • With the above state equation, you can use dynamic programming to preprocess the string, and then traverse the string through dfs traversal (surface traversal F(i,j), in essence, divide the string into all sub The string then judges whether the current string meets the conditions of the palindrome string (here there is a preprocessing of dynamic programming, and the time complexity of the judgment condition is O(1))
  • The recursive method (dfs) for dividing all substrings of a string can be seen in this article, and the principle is the same https://blog.csdn.net/lr_shadow/article/details/113934454
class Solution {
    
    
public:
    vector<vector<string>> ans;
    vector<vector<int>> f;
    vector<string> ret;
    int n;

    void dfs(const string& s, int i){
    
    
        if(i == n){
    
    
            ans.push_back(ret);
            return;
        }
        for(int j = i ; j < n; ++j){
    
    
            if(f[i][j]){
    
    
                ret.push_back(s.substr(i, j - i + 1));
                dfs(s, j + 1);
                ret.pop_back();
            }
        }
    }

    vector<vector<string>> partition(string s) {
    
    
        n = s.length();
        f.assign(n, vector<int>(n, true));

        for(int i = n - 1 ; i >= 0 ; --i){
    
    
            for(int j = i + 1 ; j < n ; ++j){
    
    
                f[i][j] = f[i + 1][j - 1] && (s[i] == s[j]);
            }
        }
        dfs(s, 0);
        return ans;
    }
};

Complexity analysis

  • Time complexity: O(n*2^n). n is the length of the string. In the worst case, s contains n exactly the same characters. There are a total of 2^n-1 cases. Each case requires O(n) time to construct
  • Space complexity: O(n*2^n). In the worst case, it is divided into 2^n-1 strings, each of which requires O(n) space

Backtracking + memory search

  • Note that the memoized search will not traverse all the divided subsets, so there is no need to initialize True. It is enough to maintain a two-dimensional array to record the traversed records. If a certain traversal has not been traversed, the record will be refreshed. If there is currently Record, directly return the function value corresponding to the record, memory search can reduce the time complexity of recursion and reduce unnecessary traversal, such as the classic Fibonacci problem
class Solution {
    
    
public:
    vector<vector<string>> ans;
    vector<vector<int>> f;
    vector<string> ret;
    int n;

    void dfs(const string& s, int i){
    
    
        if(i == n){
    
    
            ans.push_back(ret);
            return;
        }
        for(int j = i ; j < n; ++j){
    
    
            if(IsPalindrome(s, i, j) == 1){
    
    
                ret.push_back(s.substr(i, j - i + 1));
                dfs(s, j + 1);
                ret.pop_back();
            }
        }
    }
    // s.substr(i, j)   string: s    index: i, j
    // 0 是未搜索       1 是回文串    -1 是非回文串
    int IsPalindrome(const string& s, int i, int j){
    
    
        // 当前已被搜索过了返回1或-1
        if(f[i][j])
            return f[i][j];
        if(i >= j)
            return f[i][j] = 1;
        return f[i][j] = ((s[i] == s[j]) ? IsPalindrome(s, i + 1, j - 1) : -1);
    }

    vector<vector<string>> partition(string s) {
    
    
        n = s.length();
        // 此处不要写成vector<int>(n, true)
        // 因为记忆化搜索不会遍历所有子串
        f.assign(n, vector<int>(n));

        dfs(s, 0);
        return ans;
    }
};

Complexity analysis

  • Time complexity: same as method one in the worst case
  • Space complexity: O(n*2^n). Same as method one in the worst case
  • Although the worst time and space complexity is the same as the method, the average time and space complexity is better than the method one in most cases.

Guess you like

Origin blog.csdn.net/lr_shadow/article/details/114482162