题目:
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.
题意:
给定两个长度相同的字符串s1和s2,判断s2是否是s1的乱串。也就是s1任意交换字母,看能不能交换成s2
解题思路:
有两种方法
第一种是用递归解决,思路理解起来比动态规划思路简单的多
简单来说就是把s1,s2截断成两半,比较左右两边是否scramble,下面是例子体现
1、当s1,s2长度为1时,直接判断s1==s2即可获得答案
2、当s1为ab,那么s2只能ab或者ba
3、
例如:
3.1、gr|eat 和 rg|eat从第2个位置进行分割,不进行交换
a1=gr b1=eat a2=rg b2=eat
此时需要判断
(gr, rg) && (eat, eat)
3.2、对于 re|at 和 ta|er从第2个位置进行分割
a1=re b1=at a2=ta b2=er,这种显然这种是不符合的
那么s2的两部分交换,那么s'=er|ta
a1=re b1=at a2`=er b2`=ta,这种显然是符合条件的
对于任意长度的字符串,我们可以把字符串s1分为a1,b1两部,s2分为a2,b2两部分
满足 (a1~a2) && (b1~b2) || (a1~b2) && (a2~b1)
4、剪枝,判断两个字符串是否有相同的字符集,没有则直接剪去这个分支
Java代码:
/*
* 递归解法
*/
public static boolean isScramble(String s1, String s2) {
if(s1.length() != s2.length()) {
return false;
}
if(s1.length() == 1) {
return s1.equals(s2);
}
//剪枝
char[] chars1 = s1.toCharArray();
char[] chars2 = s2.toCharArray();
Arrays.sort(chars1);
Arrays.sort(chars2);
String str1 = new String(chars1);
String str2 = new String(chars2);
if(!str1.equals(str2)) {
return false;
}
//比较s1,s2左右部分
for(int i = 1; i < s1.length(); i++) {
String s1l = s1.substring(0, i);
String s1r = s1.substring(i,s1.length());
String s2l = s2.substring(0, i);
String s2r = s2.substring(i,s1.length());
//没有交换
if(isScramble(s1l,s2l) && isScramble(s1r,s2r)) {
return true;
}
//交换比较
s2l = s2.substring(0,s1.length()-i);
s2r = s2.substring(s1.length() - i,s1.length());
if(isScramble(s1l,s2r) && isScramble(s1r,s2l)) {
return true;
}
}
return false;
}
下面是动态规划解法:
当时是想用二维的dp的,结果发现无法解决,看了下大佬的dp解决,发现他们是用三维的dp来解决的。感觉这题用dp是真的难,想都想不出来,看了大佬的思路还是有点不清楚
这位大佬的博客为 dp解决,下面是大佬的思路方法
用维护量res[i][j][n],其中i是s1的起始字符,j是s2的起始字符,而n是当前的字符串长度,res[i][j][len]表示的是以i和j分别为s1和s2起点的长度为len的字符串是不是互为scramble。
有了维护量我们接下来看看递推式,也就是怎么根据历史信息来得到res[i][j][len]。判断这个是不是满足,其实我们首先是把当前s1[i...i+len-1]字符串劈一刀分成两部分,然后分两种情况
第一种是左边和s2[j...j+len-1]左边部分是不是scramble,以及右边和s2[j...j+len-1]右边部分是不是scramble
第二种情况是左边和s2[j...j+len-1]右边部分是不是scramble,以及右边和s2[j...j+len-1]左边部分是不是scramble。
如果以上两种情况有一种成立,说明s1[i...i+len-1]和s2[j...j+len-1]是scramble的。而对于判断这些左右部分是不是scramble我们是有历史信息的,因为长度小于n的所有情况我们都在前面求解过了(也就是长度是最外层循环)。
上面说的是劈一刀的情况,对于s1[i...i+len-1]我们有len-1种劈法,在这些劈法中只要有一种成立,那么两个串就是scramble的。
总结起来递推式是res[i][j][len] = || (res[i][j][k]&&res[i+k][j+k][len-k] || res[i][j+len-k][k]&&res[i+k][j][len-k]) 对于所有1<=k<len,也就是对于所有len-1种劈法的结果求或运算。因为信息都是计算过的,对于每种劈法只需要常量操作即可完成,因此求解递推式是需要O(len)(因为len-1种劈法)。
如此总时间复杂度因为是三维动态规划,需要三层循环,加上每一步需要线行时间求解递推式,所以是O(n^4)。虽然已经比较高了,但是至少不是指数量级的,动态规划还是有很大优势的,空间复杂度是O(n^3)。
下面为代码实现:
public boolean isScramble(String s1, String s2) {
if(s1==null || s2==null || s1.length()!=s2.length())
return false;
if(s1.length()==0)
return true;
boolean[][][] res = new boolean[s1.length()][s2.length()][s1.length()+1];
for(int i=0;i<s1.length();i++)
{
for(int j=0;j<s2.length();j++)
{
res[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+1;i++)
{
for(int j=0;j<s2.length()-len+1;j++)
{
for(int k=1;k<len;k++)
{
res[i][j][len] |= res[i][j][k]&&res[i+k][j+k][len-k] || res[i][j+len-k][k]&&res[i+k][j][len-k];
}
}
}
}
return res[0][0][s1.length()];
}