https://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.
这道题看了半天也没看出规律,只觉得应该分成两个部分,然后每个部分是anagram, 后来看了别人的分析,确实,当问题不明朗时,就应该从小问题入手分析,看能不能找出什么规律来!“这个问题是google的面试题。由于一个字符串有很多种二叉表示法,貌似很难判断两个字符串是否可以做这样的变换。
对付复杂问题的方法是从简单的特例来思考,从而找出规律。
先考察简单情况:
字符串长度为1:很明显,两个字符串必须完全相同才可以。
字符串长度为2:当s1="ab", s2只有"ab"或者"ba"才可以。
对于任意长度的字符串,我们可以把字符串s1分为a1,b1两个部分,s2分为a2,b2两个部分,满足((a1~a2) && (b1~b2))或者 ((a1~b2) && (a1~b2))”
递归的解法还是比较简单的,递归前有一个排序对比,如果没有这一步,是time limit exceeded的
public boolean isScramble(String s1, String s2) {
if(s1==null) return s2==null;
if(s1.length() != s2.length()) return false;
if(s1.equals(s2)) return true;
char[] s1ch = s1.toCharArray();
char[] s2ch = s2.toCharArray();
Arrays.sort(s1ch);
Arrays.sort(s2ch);
if(!new String(s1ch).equals(new String(s2ch))){
return false;
}
for(int i=1; i<s1.length(); i++){
String sub11 = s1.substring(0, i);
String sub12 = s1.substring(i);
String sub21 = s2.substring(0, i);
String sub22 = s2.substring(i);
if(isScramble(sub11, sub21) && isScramble(sub12, sub22)) return true;
sub21 = s2.substring(0, s2.length()-i);
sub22 = s2.substring(s2.length()-i);
if(isScramble(sub11, sub22) && isScramble(sub12, sub21)) return true;
}
return false;
}
递归的时间复杂度:“递归算法在最差情况下应该是O(3^n),而非O(n^2)。理由是:假设函数运行时间为f(n),那么由于在每次函数调用中都要考虑1~n之间的所有长度,并且正反都要检查,所以有
f(n) = 2[f(1) + f(n-1)] +2[f(2) + f(n-2)] … + 2[f(n/2) + f(n/2+1)]. 易推得f(n+1) = 3(fn), 故f(n) = O(3^n)。”
"dp[i][j][k] 代表了s1从i开始,s2从j开始,长度为k的两个substring是否为scramblestring。
有三种情况需要考虑:
1. 如果两个substring相等的话,则为true
2. 如果两个substring中间某一个点,左边的substrings为scramble string,同时右边的substrings也为scramble string,则为true
3. 如果两个substring中间某一个点,s1左边的substring和s2右边的substring为scramblestring, 同时s1右边substring和s2左边的substring也为scramblestring,则为true"
下面是3维DP的解法,O(n^3),简直写得伤心。。。。
public class Solution {
public boolean isScramble(String s1, String s2) {
if(s1==null) return s2==null;
if(s1.length() != s2.length()) return false;
if(s1.equals(s2)) return true;
int len1 = s1.length();
int len2 = s2.length();
boolean[][][] dp = new boolean[len1][len2][len1];
for(int i=0; i<s1.length(); i++){
for(int j=0; j<s1.length(); j++){
if(s1.charAt(i) == s2.charAt(j)){
dp[i][j][0] = true;
}
else dp[i][j][0] = false;
}
}
for(int k=1; k<s1.length(); k++){
int len = k+1;
for(int i=0; i<=s1.length()-len; i++){
for(int j=0; j<=s2.length()-len; j++){
String sub1 = s1.substring(i, i+len); <span style="font-family: Arial, Helvetica, sans-serif;">//这一步对比子串是可要可不要的</span>
String sub2 = s2.substring(j, j+len);
if(sub1.equals(sub2)){
dp[i][j][k] = true;
continue;
}
for(int t=1; t<len; t++){
if(dp[i][j][t-1] && dp[i+t][j+t][len-t-1]){
dp[i][j][k] = true;
break;
}
else if(dp[i+t][j][len-t-1] && dp[i][j+len-t][t-1]){
dp[i][j][k] = true;
break;
}
//else dp[i][j][k] = false;
}
}
}
}
return dp[0][0][s1.length()-1];
}
}