1

1. 题目

Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively.

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".

Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1.

题目的样例是从中间开始分割做树,题意其实是说可以从任意点开始切割作为树的根节点,其他非叶子的子节点也是一样可以从任意点切割。

2. 思路

第一反应是直接采用递归计算,由于有大量的重复计算,超时了。
改用迭代的方式,即用数组保存下全部的以及计算过的子串的匹配结果,直接提取结果,而不是重复计算。然后在迭代是从小往大的计算,每次都可以直接复用小的计算结果。

初始条件:当长度为1时,如果字符串相等就符合,负责不符合。
迭代:当长度为n时,将字符串表示的树的根节点放在第2至n-1元素上。某一边的子树为空时无意义的,因为就是自身。也就是长度为n的字符串有n-1种情况,分别是1,k,k的取值范围是1到n-1。这n-1中情况,只要有一种符合,就说明这个长度为n的字符串符合。
每一种情况,又有两种方式,s1的前k个和s2的前k个比较,s1后n-k个和s2的后n-k比较。也可以是s1的前k个和s2的后k个比较。两种方式对应的就是这个子树不交换和交换的场景。

3. 代码

耗时:16ms

class Solution {
public:
    bool isScramble(string s1, string s2) {
        const int len = s1.length();
        if (len != s2.length()) { return false; }
        // 非字符集合相等的提前剪枝
        string sort_s1 = s1; sort(sort_s1.begin(), sort_s1.end());
        string sort_s2 = s2; sort(sort_s2.begin(), sort_s2.end());
        if (sort_s1 != sort_s2) { return false; }
        // dp[n][i][j]表示s1[i..i+n-1]和s2[j..j+n-1]两个子串是否是scramble的
        // 迭代起点,对长度为1的直接比较计算出结果; 其他的初始化为false
        bool*** dp = new bool**[len+1];
        for (int i = 0; i < len+1; i++) {
            dp[i] = new bool*[len];
            for (int j = 0; j < len; j++) {
                dp[i][j] = new bool[len];
                for (int k = 0; k < len; k++) {
                    if (i == 0 || i == 1 && s1[j] == s2[k]) {
                        dp[i][j][k] = true;
                    } else {
                        dp[i][j][k] = false;
                    }
                }
            }
        }
        // 依次计算长度更大的, 对于长度为n的情况,直接看所有的子串划分情况是否有一个满足的
        // 由于n是递增的,因此子串的都是已经计算过的
        for (int n = 2; n < len+1; n++) {
            for (int i = 0; i <= len - n; i++) {
                for (int j = 0; j <= len - n; j++) {
                    // 计算长度为n的, s1[i, i+n-1]和s2[j, j+n-1]是否是scramble的
                    // 通过遍历所有子串划分是否存在有一个满足即可
                    for (int k = 1; k < n; k++) { // k是s1部分的左子串的长度
                        if (dp[k][i][j] && dp[n-k][i+k][j+k]
                                || dp[k][i][j+n-k] && dp[n-k][i+k][j]) {
                            dp[n][i][j] = true;
                            break;
                        }
                    }
                }
            }
        }
        return dp[len][0][0];
    }
};

knzeus
72 声望28 粉丝

行万里路,读万卷书