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

 

题意:

给定两个长度相同的字符串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()];
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值