LeetCode OJ - 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.

For example, given s = "aab",
Return

  [
    ["aa","b"],
    ["a","a","b"]
  ]

       本题同Word Break II有异曲同工之妙,对单词进行划分。题目涉及到递归、分治、回溯、DFS、DP、二维迭代这些知识,相当经典,下面详细分析以总结最近的算法练习。

        如何下手:先考虑普通的分割思路,以【ababaaaa】为例来说明。

        第一步找出所有以a开头的回文做分割,得到1.【a  babaaaa】、2.【aba  baaaa】、3.【ababa  aaa】;

                    接着处理1.【a  babaaaa】中的子串【babaaaa】,对其做开头的回文分割...递归过程一直处理下去

                    接着处理2.【aba  baaaa】中的子串【baaaa】,对其做开头的回文分割...递归过程一直处理下去

                    接着处理3.【ababa  aaa】中的子串【aaa】,对其做开头的回文分割...递归过程一直处理下去

         不难看出这是一个递归的过程,而且是一个深度遍历的过程。每做一次头部回文分割,得到的是一颗子树,需要对这颗子树进行遍历。由此可以轻松写出DPS算法。 设ret保存最后结果,item代表其中某一种回文分割, index表示子串的其实位置:调用DFS(s, 0, item)

         void DFS(s, index, item) {
              if(index == s.size()) {
                  ret += item;
              }
              for(i = index; i < s.size(); i++) {
                  if(s[index~i]为回文) {
                      item += s[index~i];
                      DFS(s, i+1, item);
                      item -= s[index~i];
                  }
              }
         }
        DFS解释:DFS首先为递归过程,先考虑for循环,它代表了对树形结构所有子树的访问,即 1.【a  babaaaa】、2.【aba  baaaa】、3.【ababa  aaa】这三种以a开头做回文分割的情况。边界条件为,当访问的字符到达s末尾位置时,表示分割完成。
         这里有一点需要解释,for循环中的 i 采用递加的方式去遍历以s[index]开头的回文,而人的思考方式看起来并不是这样。人的思考方式是“跳跃”的,好像在 【ababaaaa】一下子就找到了a、aba、ababa三个开头回文,并且能判断ababaaaa不是回文。而实际上这种工作计算机去执行的时候,必须顺序扫描访问。PS:人有可以先有感觉,然后做出理性判断;机器的只能机械的一步一步做判断。不然我们可以写成for(X in s[index~n]) ,其中X为开头回文。

         每一次去做s[index~i]为回文的判断很耗时,同时其中也有重复的计算问题:S[i~j]是否为回文可以通过判断S[i]和S[j]以及S[i+1, j-1]是否为回文来判断。设pal[i][j]记录S[i~j]是否为回文,现在考虑提前计算出任意 i 位置到 j 位置是否为回文。分析可知这里可以用分治法考虑,即S[i~j]与S[i+1, j-1]关系,同时其中的冗余问题让我们想到了用DP可以节省计算时间。

        分析S[i~j]与S[i+1, j-1]:

        分治问题可以用递归和迭代两种,如果是递归考虑问题时从大问题到小问题考虑,如果是迭代应该从小问题到大问题逐步增大规模。现在考虑用迭代方法从边界在是出发。若计算S的某一种分词情况,最后会逐步考虑到最后一个字母,迭代从最后一个字母开始 for(i = len -1; i >=0; i++) {s[i]...},其中子问题为S[i~n]中所有的pal[i][j]其中 j 范围为[i, n-1]。

       我们考虑i = 10时,需要计算出所有10到n的回文判断,即pal[10][10] 、pal[10][11] 、pal[10][12] 、... 、pal[10][n-1]。

       我们考虑到i = 9时,需要计算出所有9到n的回文判断,即pal[9][9] 、pal[9][10] 、pal[9][11] 、... 、pal[9][n-1]。

       由于i = n-1的情况是很容易知晓的,那么得出i = n - 2的情况也很容易。也就是说计算i = 9时,提前知道了i = 10的情况,i= 9回文判断可以借助i = 10计算出的结果。 j 的范围可以从 i 取到 n -1, 对于每一个j 都有:pal[i][j]的判断需要依靠pal[i+1][j-1]

       由此二维的迭代很容易写出来。边界细节可以仔细考虑。

for(i = len - 1; i >=0; i++) {
    for(j = i; j < len; j++) {
        if(s[i] = s[j] && (j - i < 2 || s[i+1][j-1])) {
            pal[i][j]
        }
    }
}
          二维迭代似乎难理解,其实也可以看成是一维迭代。要计算出S[i~n]的所有情况,先得计算出S[i+1 ~ n ]的情况。而其中前者的S[i~j]又依赖于后者的S[i+1, j-1]。

         至此,上述文字记录了从无动态规划的递归考虑,到加入动态规划结果的递归考虑,表明了DP并不是空穴来风,凭空设想出来的,一定是由问题的需求考虑出来的:问题是否可以大化小,是否有冗余问题。同时解释了二维迭代的过程,现将二维迭代看成一维迭代能更容以理解问题。

class Solution {
    vector<vector<string>> retVString;
    bool palin[1500][1500];
public:
    vector<vector<string>> partition(string s) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        if(s.size() == 0)
            return vector<vector<string>>();
        int leng = s.size();
        for(int i = 0; i < leng; i++)
            for(int j = 0; j < leng; j++)
                palin[i][j] = false;

        for(int i = leng-1; i >= 0; i--){
            for(int j = i; j < leng; j++){
                if(s[i] == s[j] && (j-i<2 || palin[i+1][j-1])){
                    palin[i][j] = true;
                }
            }
        }
        retVString.clear();
        dfs(s, 0, vector<string>());
        return retVString; 
    }
    void dfs (string& s, int start, vector<string> palinStr)
    {
        if(start == s.size())
        {
            retVString.push_back(palinStr);
        }
        for(int i = start; i < s.size(); i++)
        {
            if(palin[start][i])
            {
                palinStr.push_back(s.substr(start, i - start + 1));
                dfs(s, i+1, palinStr);
                palinStr.pop_back();
            }
        }
    }
};


其中DP的过程也可以顺着进行:

                   for(int i = 0; i <= leng - 1; i++){
                      for(int j = i; j >= 0; j--){
                          if(s[i] == s[j] && (i-j <2 || palin[j+1][i-1])){
                               palin[j][i] = true;
                          }
                      }
                   }





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值