LeetCode分割字符串+回文题小结(回溯剪枝、深度优先搜索+记忆数组)

131. 分割回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

原题链接

解题思路: 本题解题的方法是回溯剪枝解法,解题的形式这里给出两种,即截取子串组合[参考博客题139. 单词拆分]和路径伸缩(常规的回溯写法)第一种形式解法直接给出代码,思路见参考博客总结,思路类似。

class Solution {
public:
    bool isPalindrome(string s) {
        int left = 0, right = s.size() - 1;
        while (left < right) {
            if (s[left++] != s[right--]) return false;
        }
        return true;
    }
    vector<vector<string>> partition(string s, unordered_map<string, vector<vector<string>>> &m) {
        if (s.empty()) return {};
        if (m.count(s)) return m[s];
        vector<vector<string>> res;
        for (int i = 1; i <= s.size(); ++i) {
            string sub = s.substr(0, i);
            if (isPalindrome(sub)) {
                if (sub == s) {
                    res.push_back({sub});
                    break;
                }
                vector<vector<string>> t = partition(s.substr(i));
                for (auto &a : t) {
                    a.insert(a.begin(), sub);
                    res.push_back(a);
                }
            }
        }
        return m[s] = res;
    }
    vector<vector<string>> partition(string s) {
        unordered_map<string, vector<vector<string>>> m;
        return partition(s, m);
    }
};

第二种形式解法是采取路径伸缩,在深度优先遍历过程中维护一个out数组,把它想象成一个可伸缩的遍历轨迹(路径),若能遍历到终点,则这个遍历轨迹上的序列即可作为结果集中一个元素。

class Solution {
public:
    bool isPalindrome(string s) {
        int left = 0, right = s.size() - 1;
        while (left < right) {
            if (s[left++] != s[right--]) return false;
        }
        return true;
    }
    void helper(string &s, int i, vector<string> out, vector<vector<string>>& res) {
        if (i >= s.size()) res.push_back(out);
        for (int j = i; j < s.size(); ++j) {
            string sub = s.substr(i, j - i + 1);
            if (isPalindrome(sub)) {
                out.push_back(sub);
                helper(s, j + 1, out, res);
                out.pop_back();
            }
        }
    }
    vector<vector<string>> partition(string s) {
        vector<vector<string>> res;
        helper(s, 0, {}, res);
        return res;
    }
};

132. 分割回文串 II

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回符合要求的最少分割次数。

原题链接

解题思路: 如果将题131的解法略微调整用到此题中,对应的解法复杂度太高,OJ会TLE,联想到「 求元素序列所有情况可以考虑用暴力搜索,求元素是否满足某种特征或者个数,即对应一个值,可以考虑用动态规划」,OK,此题依然可以这么考虑,定义dp[i]为字符串[0,i]使得每个子串为回文传所做的最少分割次数,那么对[0,i]内每个字符都做分割,分割的最少次数即为dp[i],每个dp[i]的初始值为i,因为对于长度为(i+1)的字符串,最多做i次分割即可使每个子串为回文串。在判断回文串时,再次使用动态规划,定义palindrome[i][j]为[i,j]字符串是否为回文,状态移动方程为palindrome[i][j]=(s[i]==s[j]&&palindrome[i+1][j-1]).

思路参考自博客.

class Solution {
public:
    int minCut(string s) {
        if (s.empty()) return 0;
        int n = s.size();
        vector<vector<bool>> palindrome(n, vector<bool>(n));
        vector<int> dp(n);
        for (int i = 0; i < n; ++i) {
            dp[i] = i;
            for (int j = 0; j <= i; ++j) {
                if (s[i] == s[j] && (i - j < 2 || palindrome[j + 1][i - 1])) {
                    palindrome[j][i] = true;
                    dp[i] = (j == 0) ? 0 : min(dp[i], dp[j - 1] + 1);
                }
            }
        }
        return dp[n - 1];
    }
};

647. 回文子串

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。

题目链接

注:此题作为题132解法的补充,dp[i][j] = s[i] == s[j] &&dp[j + 1][i - 1]),此状态转移方程要保证在更新dp[i][j]值之前dp[j + 1][i - 1]要被更新过,之前的做法是用长度来控制,依次更新长度为1,2,…,n的字符串,这样能保证在更新长度为len之前,长度为len-2的已经被更新。但是此题的做法也可以保证这点,可以这么分析,我们先遍历[0,i],这个过程会遍历i-1,同时在遍历[0,i-1]过程中,也会遍历j+1(如果在[0,i-1]范围内),因此在更新dp[j][i]时,边界j+1和i-1已经被更新过.

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size(), res = 0;
        vector<vector<bool>> dp(n, vector<bool>(n));
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j <= i; ++j) {
                if (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1])) {
                    dp[j][i] = true;
                    ++res;
                }
            }
        }
        return res;
    }
};

——————————————

写在后面

对于字符串的一般灵活度比较大,很难总共出一个不变的模板,比较难的题一般优先思考DP解法,DFS解法,还有Two pointer等等.

你可能感兴趣的:(字符串,回溯剪枝)