leetcode:131. 分割回文串

题目解析

  • leetcode:palindrome-partitioning

题目描述

leetcode:131. 分割回文串_第1张图片

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

    }
};

题目解析

题目意思:切割字符串s,切出的每一个子串都必须是回文串,请找出所有切分的可能。

分析

本题这涉及到两个关键问题:

  • 切割问题,有不同的切割方式
  • 判断回文

关键在于怎么切割。

leetcode:131. 分割回文串_第2张图片

leetcode:131. 分割回文串_第3张图片

leetcode:131. 分割回文串_第4张图片

我们用指针start试着去切割,切出一个回文串,基于新的start,继续往下切,直到start越界。

  • 切出的子串满足回文,就将它加入部分解tmp数组,并继续往下切割
  • 切出的子串不是回文,跳过该选择,不递归,继续下一轮迭代

为什么要回溯

因为不是找到一个合法的部分解就完事,要找齐所有合法的部分解。

下面两种情况,是结束递归的两种情况:

  • 指针越界了,没有可以切分的子串了,递归到这一步,说明一直在切出回文串,现在生成了一个合法的部分解,return
  • 走完了当前递归的for循环,考察了基于当前start的所有的切分可能,当前递归自然地结束

它们都代表了,当前做出的选择,所进入的递归,结束了,该分支的搜索结束了,该去搜另一分支了

所以当前递归结束后,要将当前的选择撤销,回到选择前的状态,去考察另一个选择,即进入下一轮迭代,尝试另一种切分的可能

class Solution {
    bool ispalindrome(const std::string &s, int l, int r){
        while (l <= r){
            if(s[l] != s[r]){
                return false;
            }
            ++l;
            --r;
        }
        return true;
    }

    void dfs(const std::string &s, int l, vector<string> &cur,
             vector<vector<string>> &res){
        if(l == s.size()){
            res.push_back(cur);
            return;
        }

        for (int r = l; r < s.size(); ++r) {   // 枚举出当前的所有选项,从索引start到末尾索引
            if(ispalindrome(s, l, r)){  // 当前选择i,如果 start到 i 是回文串,就切它
                cur.push_back(s.substr(l, r + 1 - l)); // 切出来,加入到部分解temp
                dfs(s, r + 1, cur, res);// 基于这个选择,继续往下递归,继续切
                cur.pop_back();  // 上面递归结束了,撤销当前选择i,去下一轮迭代
		}
            }
        }
    }
public:
    vector<vector<string>> partition(string s) {
        vector<vector<string>> res;
        vector<string> cur;
        dfs(s, 0, cur, res);
        return res;
    }
};

存在什么问题

每次递归都调用isPali判断,是否有必要?以“aab”为例,我们打印一下传入 isPali 的 start 和 i:
0 0
1 1
2 2
1 2
0 1
2 2 (重复)
0 2

我们做了重复的计算,有的子串已经判断过是否回文了,就别再判断了。

做法是用一个memo二维数组,将计算过的子问题结果存下来,下次再遇到就直接拿出来用。

加入记忆化代码

动态规划写法

  • f[i][j]表示[i, j]这一段是否为回文串
  • 要想f[i][j]为回文串,必须满足如下两个条:
    • f[i + 1][j - 1] == true
    • s[i] == s[j]
  • 由于状态f[i][j]依赖于f[i + 1][j - 1],因此我们左端点i是从大到小的,右端点j是从小到大
class Solution {

public:
    vector<vector<string>> partition(string s) {
        vector<vector<string>> res;
        vector<string> cur;
        int n = s.size();
        if(n == 0){
            return res;
        }
        std::vector<std::vector<int>> f(n,std::vector<int>(n, true));

        for (int i = n - 2; i >= 0; --i) {
            for (int j = i + 1; j < n; ++j) {
                if(s[i] != s[j]){
                    f[i][j] = false;
                }else{
                    f[i][j] = f[i + 1][j - 1];
                }
            }
        }

        std::function<void(int)>dfs = [&](int i){
            // 递归终止条件为 i指针已到字符串最后一个字符 (终止本次递归,因为是循环 所有有多条线路递归)
            if(i == n){
                res.push_back(cur);
                return ;
            }

            // 循环的目的是 可以取到字符串s的起始位置为i的所有子字符
            for (int j = i; j < n; ++j) {
                if(f[i][j]){      // 如果是子串会进入下一层
                    cur.push_back(s.substr(i, j - i + 1));
                    dfs(j + 1);
                    cur.pop_back();  // 本次递归完需要回到本次循环的上一层状态(回溯)
                }
            }
        };

        dfs(0);

        return res;
    }
};

总的来说,切割方案就是以首个字符为起点,枚举以其开头的所有回文串方案,加入集合,然后对生效的字符串部分继续暴力搜索

参考

  • Play-Leetcode

类似题目

题目 思路
leetcode:131. 切割str,使得每个子串均是回文,返回所有切割方案Palindrome Partitioning 切割方案就是以首个字符为起点,枚举以其开头的所有回文串方案,加入集合,然后对生效的字符串部分继续暴力搜索
leetcode:132. 分割str,使得每个子串均是回文,返回符合要求的最少切割次数 Palindrome Partitioning II 从左到右尝试,尝试以xxx作为切出来的第一个部分,所有的可能性,dp[i]表示:从i…开始至少分成几个部分能让每个部分都是回文串
leetcode:647. str中有多少个回文子串 Palindromic Substrings 定义:dp[i][j]表示字符串s在[i, j]区间的子串是否是一个回文串;同时维护一个ans,如果发现了true,那么ans++
leetcode:5. str中最长回文子串 Longest Palindromic Substring 定义:dp[i][j]表示字符串s在[i, j]区间的子串是否是一个回文串;两个变量: maxLen:统计最长的回文子串的长度(j-i), str:最长回文子串是什么
leetcode:516. str中最长回文子序列的长度 Longest Palindromic Subsequence 定义:在str[L…R]区间中,返回其最长回文子序列长度。那么base case:如果L == R,那么返回1;如果L + 1 == R,如果str[L] == str[R],那么返回2,否则返回1;一般情况:最长回文子串既不以L开头,也不以R结尾;最长回文子串以L开头,不以R结尾;最长回文子串不以L开头,以R结尾;最长回文子串以L开头,以R结尾,前提是str[L] == str[R]
leetcode:730. str中回文子序列的个数 Count Different Palindromic Subsequences 方法一:对于每个字符都有要和不要两种选择,最终再判断是不是回文串;方法二:定义从str[i…j]的所有子序列中能搞出多少个回文来,空串不算,一共有四种可能:回文子序列一定不选择str[i],也不选择str[j];回文子序列一定不选择str[i],一定选择str[j];回文子序列一定选择str[i],一定不选择str[j];回文子序列一定选择str[i],一定选择str[j]
leetcode:1143. str1和str2的最长公共子序列 Longest Common Subsequence 方法一:想要求s1和s2的最长公共子序列,不妨称这个子序列为 lcs。那么对于 s1 和 s2 中的每个字符,有什么选择?很简单,两种选择,要么在 lcs 中,要么不在。所以可以这样做:用两个指针i和j从后往前遍历s1和s2,如果s1[i] == s2[j],那么这个字符一定在lcs中。否则,s1[i]和s2[j]这两个字符至少有一个不在lcs中,需要丢弃一个。对于第一种情况,找到一个 lcs 中的字符,同时将 i j 向前移动一位,并给 lcs 的长度加一;对于后者,则尝试两种情况,取更大的结果。方法二:现在我们只关心str1[0…i],str2[0…j],对于它们的最长公共子序列长度是多少
1682-VIP. 最长回文子序列 II Longest Palindromic Subsequence II

你可能感兴趣的:(算法与数据结构,leetcode,算法,职场和发展)