Leetcode-131 分割字符串 && Leetcode-132 分割字符串II

最近两日的每日一题题目相似,其实方法也可以用回溯。但是Leetcode-132中,如果用之前的回溯会发现超时,此时对于回文数的判定需要用动态规划的方法减少时间复杂度。

Leetcode-131

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

本题其实与Leetcode-46 全排列类似,可以用回溯的方法进行遍历,得到所有排列组合。本题与Leetcode-46不同的是,遍历的方法不同,这也是错了很多次的地方。全排列只需要每次遍历的数据个数为1,而本题遍历的字符串的长度可以从1到 n 不等,因此dfs的参数尤为关键。
整体遍历顺序较为清晰,需要从第0个字节开始,假设到第 i 个字节为回文串,则下一次的遍历从第 i + 1个字节开始;在回溯的过程中,则判断从第 0 个字节到第 i + 1个字节的字符串是否为回文串。由于我们需要所有分割的字符串均为回文串,也就是当某一段字符串不为回文串,那么接下来的所有字符串均不需要考虑。
在一开始,dfs的参数选择为字符串的起始位置begin与结束位置end,下一次遍历很明显将会是以end + 1开始进行遍历,这样会导致一个问题,那就是无法遍历begin到 end + 1 的字符串,除非我们在main函数中对以0开始的字符串进行for循环遍历(或许还有别的方法,但是我想不出来),但是在之前的题目中,每增加一个特判总是会出现意想不到的情况,因此我对dfs的参数重新进行了选择。
这次我选择dfs的参数只有字符串开始位置begin,这样的好处就是我可以遍历所有以begin开始的字符串,不会出现上述问题,接下来就顺理成章了,代码如下所示:

class Solution {
public:
    int len;
    vector<vector<string>> v1;
    vector<string> v2;
    vector<vector<string>> partition(string s) {
        len = s.size();
        dfs(0, s);
        return v1;
    }

    void dfs(int begin, string s){
        if(begin == len){
            v1.push_back(v2);
            return ;
        }else{
            for(int i = begin; i < len; i++){
                if(ishuiwen(begin, i, s)){
                    v2.push_back(s.substr(begin, i - begin + 1));
                    dfs(i + 1, s);
                    v2.pop_back();
                }
            }
        }
    }

    bool ishuiwen(int begin, int end, string s){
        while(begin <= end){
            if(s[begin] != s[end])
                return false;
            begin++;
            end--;
        }
        return true;
    }
};

算法的时间复杂度为O((n ^ 2 )* (2 ^ n)):很明显,每一个字符组成的字符串均为回文串,因此我们会遍历任意的顺序字符串,而这些字符串的个数为2 ^ (n - 1)。同时,每一种划分方法需要 O(n ^ 2)的时间求出答案。
空间复杂度为O(n^2)。

Leetcode-132

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

返回符合要求的 最少分割次数 。
输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
提示:
	1 <= s.length <= 2000
	s 仅由小写英文字母组成

本题明显为上面问题的变式,只不过按照提示,字符串的长度最大可达到2000,很明显,时间会超出。
我想了很长时间都没有相出特别好的解法,直到看到了题解,发现题解是优先判断出所有的回文串,其时间复杂度为O(n ^ 2)。
其原理很简单,假设第 i 到第 j 个字符组成的字符串是回文串,那么当第 i - 1个字符与第j + 1个字符相等时,第 i - 1 到第 j + 1个字符组成的字符串也是回文串。其实现代码如下:

vector<vector<int>> g(n, vector<int>(n, true));
for (int i = n - 1; i >= 0; --i) {
	for (int j = i + 1; j < n; ++j) {
		g[i][j] = (s[i] == s[j]) && g[i + 1][j - 1];
	}
}

在回文串全部判定之后,其题目就较为明显了,其解法与Leetcode-300 最长递增子序列类似。以dp[i]表示第 i 个字符结尾的字符串最少可由多少个回文串组成。假如第 a 个字符到第 b 个字符组成的字符串是回文串,那么dp[b]可以由dp[a - 1] + 1表示,这样我们可以判断所有以第 b 个字符结尾的回文串,从而获取其最小表示,因此问题的答案其实就是dp[n - 1],其中 n 是字符串的个数。
代码如下:

class Solution {
public:
    int len;
    int minn = INT_MAX;
    vector<int> v2;
    vector<vector<bool>> v1;
    //判断回文数预处理
    void judge(string s){
        for(int i = len - 1; i >= 0; i--){
            for(int j = i + 1; j < len; j++){
                v1[i][j] = (s[i] == s[j]) && v1[i + 1][j - 1];
            }
        }
    }

    int minCut(string s) {
        len = s.size();
        v2.assign(len, INT_MAX);
        v1.assign(len, vector<bool>(len, true));
        judge(s);
        for(int i = 0; i < len; i++){
            if(v1[0][i] == true){
                v2[i] = 0;
            }else{
                for(int j = 0; j < i; j++){
                    if(v1[j + 1][i])
                        v2[i] = min(v2[i], v2[j] + 1);
                }
            }
        }

        return v2[len - 1];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值