leetcode[86] Scramble String

将一个单词按照这种方式分:

Below is one possible representation of s1 = "great":

    great
   /    \
  gr    eat
 / \    /  \
g   r  e   at
           / \
          a   t

To scramble the string, we may choose any non-leaf node and swap its two children.

For example, if we choose the node "gr" and swap its two children, it produces a scrambled string "rgeat".

    rgeat
   /    \
  rg    eat
 / \    /  \
r   g  e   at
           / \
          a   t

We say that "rgeat" is a scrambled string of "great".

Similarly, if we continue to swap the children of nodes "eat" and "at", it produces a scrambled string "rgtae".

    rgtae
   /    \
  rg    tae
 / \    /  \
r   g  ta  e
       / \
      t   a

We say that "rgtae" is a scrambled string of "great".

这样就说他们是scrambled string。现在给你两个字符串怎么判断是否属于scrambled string呢

思路:

想了n久,木有什么好的idea。

然后学习了justdoit兄的博客,理解了后自己也跟着写了个递归的。

简单的说,就是s1和s2是scramble的话,那么必然存在一个在s1上的长度l1,将s1分成s11和s12两段,同样有s21和s22。
那么要么s11和s21是scramble的并且s12和s22是scramble的;
要么s11和s22是scramble的并且s12和s21是scramble的。

判断剪枝是必要的,否则过不了大数据集合。

class Solution {
public:
bool subScramble(string s1, string s2)
{
    if (s1 == s2) return true;
    int len = s1.size();
    string sort_s1 = s1, sort_s2 = s2;
    sort(sort_s1.begin(), sort_s1.end());
    sort(sort_s2.begin(), sort_s2.end());
    if (sort_s1 != sort_s2) return false; //如果字母不相等直接返回错误
    for (int i = 1; i < len; ++i)
    {
        string s1_left = s1.substr(0,i);
        string s1_right = s1.substr(i);
        string s2_left = s2.substr(0,i);
        string s2_right = s2.substr(i);

        if (subScramble(s1_left, s2_left) && subScramble(s1_right, s2_right))
            return true;

        s2_left = s2.substr(0, len - i);
        s2_right = s2.substr(len - i);

        if (subScramble(s1_left, s2_right) && subScramble(s1_right, s2_left))
            return true;
    }
    return false;
}
bool isScramble(string s1, string s2)
{
    return subScramble(s1, s2);
}
};

 如果有更好的方法,我们一般是不用递归的,因为递归往往复杂以致难以掌控。继续学习,发现果然有动态规划的方法。凭空想,确实难以想象。但看见之后又恍然明了。

本题递归复杂度是非多项式的。具体多少您探讨一下。动态规划的复杂度应该是O(n^4),连动态规划都这个复杂度了,可见此题之可怕啊。

三维动态规划:

dp[k][i][j]: s1的第i个开始长度为k的串和s2的第j个开始长度为k的串是否scrambled,是的话true,否则存false;

同递归核心思想,dp[k][i][j]应该可以从1到k-1长度的分割来求解,一旦有一个为true,那么dp[k][i][j]就为true并跳出。

即:

dp[k][i][j] = (dp[div][i][j] && dp[k-div][i+div][j+div] || dp[div][i][j+k-div] && dp[k-div][i+div][j]);

div从1到k-1,分两种情况,即s1的从i开始的div个和s2从j开始的div个scrambled并且从i+div开始的k-div个都匹配那么true,

同理,如果s1的i开始的div个和s2的后div个匹配以及剩下另外两部分也都匹配,那么true。直到找到true位置。如下代码,找到true后,for中的判断!dp[k][i][j]不成立就跳出了。

class Solution {
public:
// 动态规划 dp[k][i][j]存s1从第i个开始长度为k的串和s2从j开始长度为k的串是否scrambled
bool isScramble(string s1, string s2)
{
    if (s1.size() != s2.size()) return false;
    int len = s1.size();
    bool dp[len+1][len][len];
    
    // initialization
    for (int i = 0; i < len; ++i)
        for (int j = 0; j < len; ++j)
            dp[1][i][j] = s1[i] == s2[j];
            
    // dp
    for (int k = 2; k < len + 1; ++k)
        for (int i = 0; i < len; ++i)
            for (int j = 0; j < len; ++j)
            {
                // initialization
                dp[k][i][j] = false; 
                // once dp[k][i][j] is true, jump out
                for (int div = 1; div < k && !dp[k][i][j]; ++div)
                    dp[k][i][j] = (dp[div][i][j] && dp[k-div][i+div][j+div] || dp[div][i][j+k-div] && dp[k-div][i+div][j]);
            }
    return dp[len][0][0];
}
};

以下三点值得注意:

1. 此题递归只要主要判断排序后子串是否相等,不等直接剪枝,减少计算量。

2. 两中方法都用到的核心是把两个字符串都分成两部分,如果对应匹配或者交叉匹配满足,则true。

3. 就是substr的用法,如何准确判断子串。

posted on 2014-11-20 23:46  higerzhang  阅读(208)  评论(0编辑  收藏  举报