Scramble String

来源:互联网 发布:java面试专业技能 编辑:程序博客网 时间:2024/05/22 22:10

https://oj.leetcode.com/problems/scramble-string/


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.


public boolean isScramble(String s1, String s2)

这一题,和find median in two sorted array在我眼里是并列oj所有题目top 2难度的,这一题同时也是oj里面目前为止最难的,也是唯一的三维DP。

当然,跟以往一样,在给出dp解之前,我会给出一个可以AC的暴力递归截枝解。

这题暴力的做法其实不是很难。主要是在每一个递归层级的当前s1和s2 中,遍历一个分界点,也就是取一个i,  0 < i < s.length(),然后进行两次分组。

第一个分组是s1[0.. i - 1], s2[0.. i - 1]的搭配以及s1[i...s1.length() - 1], s2[i...s2.length() - 1]的搭配

第二个分组是s1[0...i - 1] s2[s2.length() - i , s2.length() - 1]的搭配以及s1[i.. s1.length() - 1]. s2[0... s2.length() - i - 1]的搭配。

以上两组共四个搭配分别往下递归。也就是每组两次,分别两组递归。当第一组可以返回true的时候就不需要进行第二组了,接下来的分割也是不需要了。因为找到一组合理的就可以了。

当所有分割分组的递归都没办法返回true的时候就只能返回false了。这个递归的base case是搭配的子字符串相等即返回true。

至于截枝的方法,只能在每次开展循环分割并递归的之前,判断s1和s2的字符数目是否相等。如果不相等直接返回false就可以了。

下面给出暴力的代码:

    public boolean isScramble(String s1, String s2) {        if(s1.length() != s2.length())            return false;        return helper(s1, s2);    }        public boolean helper(String s1, String s2){        if(s1.equals(s2))            return true;                int[] counter = new int[26];        for(int i = 0; i < s1.length(); i++){            counter[s1.charAt(i) - 'a']++;        }        for(int i = 0; i < s2.length(); i++){            counter[s2.charAt(i) - 'a']--;        }        for(int i : counter){            if(i != 0)                return false;        }        for(int i = 1; i < s1.length(); i++){            String s11 = s1.substring(0, i), s12 = s1.substring(i), s21 = s2.substring(0, i), s22 = s2.substring(i);            if(helper(s11, s21) && helper(s12, s22))                return true;            String s23 = s2.substring(0, s1.length() - i), s24 = s2.substring(s1.length() - i);            if(helper(s11, s24) && helper(s12, s23))                return true;        }        return false;    }
至于DP解,其实也是基于上述暴力解的一个方案

首先给出f(i, j, k)的定义,就是当s1以第i个字母开始,s2以第j个字母开始,k长度的子串是否是scrambled的。

那么这个dp的base case是f(i, j , 1) = s1[i] == s2[j].

那么在k >= 2的环境里f(i,j,k) = f(i,j, m) && f(i + m,j + m, k - m) || f(i, j + k - m, m) && f(i + m, j,  k - m) ( 1 <= m < k)

其实上述的推导式里面在我们的暴力递归式就已经可以看到了。具体就不详述了,

    public boolean isScramble(String s1, String s2) {        if(s1.length() != s2.length())            return false;        boolean[][][] dp = new boolean[s1.length()][s1.length()][s1.length() + 1];        for(int i = 0; i < s1.length(); i++){            for(int j = 0; j < s2.length(); j++){                dp[i][j][1] = s1.charAt(i) == s2.charAt(j);            }        }        for(int len = 2; len <= s1.length(); len++){            for(int i = 0; i <= s1.length() - len; i++){                for(int j = 0; j <= s2.length() - len; j++){                    for(int k = 1; k < len; k++){                        dp[i][j][len] |= (dp[i][j][k] && dp[i + k][j + k][len - k]) // 相当于brute force的正向比较                                        || (dp[i][j + len - k][k] && dp[i + k][j][len - k]);// brute force的反向比较                    }                }            }        }        return dp[0][0][s1.length()];    }


0 0
原创粉丝点击