Leetcode:Scramble String 解题报告
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.

解答:
1. Brute Force 递归。
基本的思想就是:在S1上找到一个切割点,左边长度为i, 右边长为len - i。 有2种情况表明它们是IsScramble
(1). S1的左边和S2的左边是IsScramble, S1的右边和S2的右边是IsScramble
(2). S1的左边和S2的右边是IsScramble, S1的右边和S2的左边是IsScramble (实际上是交换了S1的左右子树)
而i的取值可以是1 ~ len-1。 基于这个思想,我们可以写出以下的递归Brute Force 解:
引自stellari对复杂度的解释:
看了你的不少文章,感觉收获良多!只是有点小问题想请教:按照我的理解,那个递归算法在最差情况下应该是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)。当然这是最差情况下的时间复杂度。那么你提到的O(n^2),是否是通过其他数学方法得到的更tight的上限?欢迎探讨!
这一个解是不能通过LeetCode的检查的,复杂度是 3^N
public static boolean isScramble1(String s1, String s2) {
if (s1 == null || s2 == null) {
return false;
}
int len1 = s1.length();
int len2 = s2.length();
// the two strings should be the same length.
if (len1 != len2) {
return false;
}
return rec(s1, s2);
}
// Solution 1: The recursion version.
public static boolean rec1(String s1, String s2) {
int len = s1.length();
// the base case.
if (len == 1) {
return s1.equals(s2);
}
// 鍒掑垎2涓瓧绗︿覆
for (int i = 1; i < len; i++) {
// we have two situation;
// the left-left right-right & left-right right-left
if (rec1(s1.substring(0, i), s2.substring(0, i))
&& rec1(s1.substring(i, len), s2.substring(i, len))) {
return true;
}
if (rec1(s1.substring(0, i), s2.substring(len - i, len))
&& rec1(s1.substring(i, len), s2.substring(0, len - i))) {
return true;
}
}
return false;
}
2. 递归加剪枝
感谢unieagle的提示,我们可以在递归中加适当的剪枝,也就是说在进入递归前,先把2个字符串排序,再比较,如果不相同,则直接退出掉。这样也能有效地减少复杂度,具体多少算不清。但能通过leetcode的检查。
// Solution 2: The recursion version with sorting.
// 鎺掑簭涔嬪悗鐨勫壀鏋濆彲浠ラ�杩嘗eetCode鐨勬鏌�
public static boolean rec(String s1, String s2) {
int len = s1.length(); // the base case.
if (len == 1) {
return s1.equals(s2);
} // sort to speed up.
char[] s1ch = s1.toCharArray();
Arrays.sort(s1ch);
String s1Sort = new String(s1ch); char[] s2ch = s2.toCharArray();
Arrays.sort(s2ch);
String s2Sort = new String(s2ch); if (!s1Sort.equals(s2Sort)) {
return false;
} // 鍒掑垎2涓瓧绗︿覆
for (int i = 1; i < len; i++) {
// we have two situation;
// the left-left right-right & left-right right-left
if (rec(s1.substring(0, i), s2.substring(0, i))
&& rec(s1.substring(i, len), s2.substring(i, len))) {
return true;
} if (rec(s1.substring(0, i), s2.substring(len - i, len))
&& rec(s1.substring(i, len), s2.substring(0, len - i))) {
return true;
}
} return false;
}
3. 递归加Memory
我们在递归中加上记忆矩阵,也可以减少重复运算,但是我们现在就改一下之前递归的结构以方便加上记忆矩阵,我们用index1记忆S1起始地址,index2记忆S2起始地址,len 表示字符串的长度。这样我们可以用一个三维数组来记录计算过的值,同样可以通过leetcode的检查。这个三维数组一个是N^3的复杂度,在每一个递归中,要从1-len地计算一次所有的子串,所以一共的复杂度是N^4
// Solution 3: The recursion version with memory.
// 閫氳繃璁板繂鐭╅樀鏉ュ噺灏戣绠楅噺
public static boolean isScramble3(String s1, String s2) {
if (s1 == null || s2 == null) {
return false;
} int len1 = s1.length();
int len2 = s2.length(); // the two strings should be the same length.
if (len1 != len2) {
return false;
} int[][][] mem = new int[len1][len1][len1];
for (int i = 0; i < len1; i++) {
for (int j = 0; j < len1; j++) {
for (int k = 0; k < len1; k++) {
// -1 means unseted.
mem[i][j][k] = -1;
}
}
} return recMem(s1, 0, s2, 0, len1, mem);
} // Solution 3: The recursion version with memory.
// 閫氳繃璁板繂鐭╅樀鏉ュ噺灏戣绠楅噺
public static boolean recMem(String s1, int index1, String s2, int index2,
int len, int[][][] mem) {
// the base case.
if (len == 1) {
return s1.charAt(index1) == s2.charAt(index2);
} // LEN: 1 - totalLen-1
int ret = mem[index1][index2][len - 1];
if (ret != -1) {
return ret == 1 ? true : false;
} // 鍒濆鍖栦负false
ret = 0; // 鍒掑垎2涓瓧绗︿覆. i means the length of the left side in S1
for (int i = 1; i < len; i++) {
// we have two situation;
// the left-left right-right & left-right right-left
if (recMem(s1, index1, s2, index2, i, mem)
&& recMem(s1, index1 + i, s2, index2 + i, len - i, mem)) {
ret = 1;
break;
} if (recMem(s1, index1, s2, index2 + len - i, i, mem)
&& recMem(s1, index1 + i, s2, index2, len - i, mem)) {
ret = 1;
break;
}
} mem[index1][index2][len - 1] = ret;
return ret == 1 ? true : false;
}
4. 动态规划。
其实如果写出了3,动态规划也就好写了。
三维动态规划题目:
我们提出维护量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
如此总时间复杂度因为是三维动态规划,需要三层循环,加上每一步需要线行时间求解递推式,所以是O(n^4)。虽然已经比较高了,但是至少不是指数量级的,动态规划还是相当有用的,空间复杂度是O(n^3)。代码如下:
注:事实上这里最大的难点,是你怎么安排这三个循环。仔细看一下,计算len对应的解时,要用到一堆len-1的解。所以我们应该len 从0到1地这要子计算(三维啊都没办法通过画图来推导动态规划的递增关系了!)
/*
* Solution 4: The DP Version.
*/
public static boolean isScramble4(String s1, String s2) {
if (s1 == null || s2 == null) {
return false;
} int len1 = s1.length();
int len2 = s2.length(); // the two strings should be the same length.
if (len1 != len2) {
return false;
} /*
* i: The index of string 1. j: The index of string 2. k: The length of
* the two string. 1 ~ len1
*
* D[i][j][k] =
*/
boolean[][][] D = new boolean[len1][len1][len1 + 1];
for (int subLen = 1; subLen <= len1; subLen++) {
for (int i1 = 0; i1 <= len1 - subLen; i1++) {
for (int i2 = 0; i2 <= len1 - subLen; i2++) {
if (subLen == 1) {
D[i1][i2][subLen] = s1.charAt(i1) == s2.charAt(i2);
continue;
} D[i1][i2][subLen] = false;
for (int l = 1; l < subLen; l++) {
if (D[i1][i2][l] && D[i1 + l][i2 + l][subLen - l]
|| D[i1][i2 + subLen - l][l] && D[i1 + l][i2][subLen - l]
) {
D[i1][i2][subLen] = true;
break;
}
}
}
}
} return D[0][0][len1];
} /*
* Solution 4: The DP Version. REDO
*/
public static boolean isScramble(String s1, String s2) {
if (s1 == null || s2 == null) {
return false;
} int len = s1.length(); if (s2.length() != len) {
return false;
} boolean[][][] D = new boolean[len][len][len + 1]; // D[i][j][k] = D[i][]
for (int k = 1; k <= len; k++) {
// 注意这里的边界选取。 如果选的不对,就会发生越界的情况.. orz..
// attention: should use "<="
for (int i = 0; i <= len - k; i++) {
for (int j = 0; j <= len - k; j++) {
if (k == 1) {
D[i][j][k] = s1.charAt(i) == s2.charAt(j);
continue;
} D[i][j][k] = false;
for (int l = 1; l <= k - 1; l++) {
if (D[i][j][l] && D[i + l][j + l][k - l]
|| D[i][j + k - l][l] && D[i + l][j][k - l] ) {
D[i][j][k] = true;
break;
}
}
}
}
} return D[0][0][len];
}
GITHUB:
Leetcode:Scramble String 解题报告的更多相关文章
- 【LeetCode】87. Scramble String 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 动态规划 日期 题目地址:https://le ...
- Leetcode:Interleaving String 解题报告
Interleaving StringGiven s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For ...
- 【LeetCode】481. Magical String 解题报告(Python)
[LeetCode]481. Magical String 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http:/ ...
- 【LeetCode】833. Find And Replace in String 解题报告(Python)
[LeetCode]833. Find And Replace in String 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu ...
- 【LeetCode】678. Valid Parenthesis String 解题报告(Python)
[LeetCode]678. Valid Parenthesis String 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人 ...
- LeetCode: Combination Sum 解题报告
Combination Sum Combination Sum Total Accepted: 25850 Total Submissions: 96391 My Submissions Questi ...
- 【LeetCode】Permutations 解题报告
全排列问题.经常使用的排列生成算法有序数法.字典序法.换位法(Johnson(Johnson-Trotter).轮转法以及Shift cursor cursor* (Gao & Wang)法. ...
- LeetCode - Course Schedule 解题报告
以前从来没有写过解题报告,只是看到大肥羊河delta写过不少.最近想把写博客的节奏给带起来,所以就挑一个比较容易的题目练练手. 原题链接 https://leetcode.com/problems/c ...
- LeetCode: Sort Colors 解题报告
Sort ColorsGiven an array with n objects colored red, white or blue, sort them so that objects of th ...
随机推荐
- 安装apr报错rm: cannot remove `libtoolT': No such file or directory
直接打开/usr/local/src/apr-1.4.6/configure 把 $RM“$cfgfile” 那行删除掉 $RM“$cfgfile” 大约在 42302行 然后再重新运行 ./co ...
- MySQL常见错误代码及代码说明
1005:创建表失败 1006:创建数据库失败 1007:数据库已存在,创建数据库失败<=================可以忽略 1008:数据库不存在,删除数据库失败<======== ...
- 73条日常shell命令汇总,总有一条你需要!
1.检查远程端口是否对bash开放: echo >/dev/tcp/8.8.8.8/53 && echo "open" 2.让进程转入后台: Ctrl + z ...
- k8s实战之数据卷(volume)
一.概述 数据卷用于实现容器持久化数据,k8s对于数据卷重新定义,提供了丰富强大的功能:数据卷分为三类: 本地数据卷,网络数据卷和信息数据卷 二.
- 解决servlet-api包冲突问题(maven)
问题描述:本人的项目是用Maven管理,而且用到了servlet3.0的技术,但是项目中用到servlet3.0的地方,总提示找不到类中的方法.很奇怪,在网上找到好多解决办法,综合一下终于解决了.现将 ...
- 基于matplotlib的数据可视化 - 热图imshow
热图: Display an image on the axes. 可以用来比较两个矩阵的相似程度 mp.imshow(z, cmap=颜色映射,origin=垂直轴向) imshow( X, cma ...
- 【转】java原理—反射机制
一.什么是反射:反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力.这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它首先被程序语言 ...
- ElasticSearch 数据增删改实现
前言 本文介绍 ElasticSearch 增加.删除.修改数据的使用示例.通过Restful 接口和 Python 实现.ES最新版本中有Delete By Query 和 Update By Qu ...
- photoshop,钢笔工具锚点类型
以下是钢笔工具绘制出的三种类型的锚点,分别是:无切线的硬锚点.左右切线平行的锚点.左右切线不平行的锚点. 使用转换点工具点击锚点,可以实现 无切线锚点 和 左右切线平行的锚点 之间的转化: 对 左右切 ...
- Windows 计划任务 Task Schedule 怎么 运行 .bat文件
1. 新建 test.bat 需要切换文件夹以后 并且执行程序. cd /D "F:\xxxfolder" && dotnet testdll.dll 2. 直接设 ...