[英]Understanding Example 12 All Permutations of a string from Big O notation - Cracking the Coding Interview
我一直不明白作者是如何得到以下过程的O(n^2 * n!)
复杂度的,该过程生成字符串的所有排列。
void permutation(String str){
permutation(str,"");
}
void permutation(String str, String prefix){
if(str.length()==0){
System.out.println(prefix);
} else{
for(int i=0;i<str.length();i++){
String rem=str.substring(0,i)+str.substring(i+1);
permutation(rem,prefix+str.charAt(i));
}
}
}
由于 else 路径成本,该方法的复杂度为O(n^2 *n!)
:
首先注意每次调用String rem=str.substring(0,i)+str.substring(i+1);
是O(n)
,在else
路径中,我们将它计算n
次,并调用具有复杂度T(n-1)
permutation
。
计算这个的复杂度等价于求解: T(n) = n[n+T(n-1)]
; n
次(for 循环)一个(n+T(n-1))
解决这个重复问题并不是那么容易,如果我没记错的话,它应该归结为解决:
但让我们尝试近似。 每个排列(基本情况)代表递归树中的一个节点。 这棵树有n!
叶子。 每个叶子都有一条到长度为n
的根的路径。 所以可以安全地假设不超过n*n!
树中的节点。
这是对permutation
调用次数的上限。 由于每个调用都花费n
,因此复杂度的总体上限为O(n^2*n!)
希望这可以帮助。
我迟到了,但我仍然会发布我的答案。
我是这样向自己解释的。 采用简化的方法,让我们忽略函数的所有步骤: permutation(String str, String prefix)
除了递归步骤。
记住上面的内容,函数的时间复杂度可以用递推关系表示: T(N) = N*T(N-1)
,其中N
是输入字符串的长度。 展开后, T(N) = N*(N-1)*(N-2)*...3*2*1*T(0)
。 --- (*)
现在, T(0) = O(N)
因为在基本情况下我们打印前缀字符串,打印长度为 N 的字符串是一个 O(N) 操作。
以封闭形式表示上面的 (*): N*N!
--- (1)
现在考虑permutation
函数的以下行: String rem = str.substring(0, i) + str.substring(i + 1);
这又是一次O(N)
操作,并且对N!
递归调用。 因此考虑以上并与上面的表达式(1)乘积,总运行时间复杂度T(N)
结果为
N*N*N! = N^2*N!
时间复杂度来自 for 循环执行的次数。 在书中这用n * n!
表示n * n!
这是一种简化。 此运行时来自图中的估计节点数。 如果您开始绘制图形,您会看到最低级别的节点数为n!
在每个级别中,它是较低级别的节点数除以2, 3, .., n
util 顶层有一个节点。
在书中,他们没有计算节点的确切数量,而是将图中的级别数乘以级别中的最大分支数,因此n * n!
. 这个数字将始终高于或等于节点的确切数量,因此它可以很好地作为上限。
然后如书中所述,for 循环体中的字符串连接将采用O(n)
因此总体时间复杂度为O(n * n * n!) = O(n^2 * n!)
出于两个原因,我们没有添加 if body 所花费的时间复杂度。 一个是主体的时间复杂度是O(1)
- 因为它只是一个打印语句。 如果主体将依赖于 n 或其他相关变量,那么我们将不得不添加该不变量和 if 主体执行次数的乘法。 其次, if 主体被执行n!
次,但我们已经将其计入 for 循环体执行的次数(有关 for 循环体的确切调用次数,请参见下面的等式)。
如果在 else 语句中但在 for 循环之外有更多代码行,我们将不得不通过将不变量和 else 循环的执行次数相乘来增加时间复杂度。
我相信我们可以使用下面的等式获得图中节点的确切数量:
然后可以通过以下方式确定 for 循环体调用的数量:
我们必须加n! 因为 for 循环另外执行了 n! 长度为零的 rem 的次数。 我们必须减去 1,因为没有为第一个节点执行 for 循环体(其中rem.length() == str.length()
)。
解决这个问题的一个简单技巧是 O(节点数 * 每个节点的计算量)。
每个节点的计算是 O(n)。
节点数可以通过对每个级别的节点求和来计算。
即(n!/1!)+(n!/2!)+(n!/3!)...+(n!/n!)
等于 n! * (1/1! + 1/2! + ... + 1/n!)
该系列的总和 (1/1! + 1/2! + ... + 1/n!) 被视为n (作为上限)
这导致节点数为 n * n!
所以,O(节点数*每个节点的计算)是O(n * n! * n),也就是O(n 2 * n!)
但是,如果我们进行真正的数学运算, (1/1! + 1/2! + ... + 1/n!) 是 1.7182 (其中 n > 6 )
因此,节点数为 1.7182 * n!。 复杂度必须是 O(1.7182 * n! * n) 即O(n * n!)
当我在代码之间使用计数时,我总是得到 n 阶乘作为步数。
public class example12 {
int count=0;
public static void main(String args[])
{
example12 a= new example12();
a.permutation("12345678", "test");
}
void permutation(String str){
permutation(str,"");
}
void permutation(String str, String prefix){
if(str.length()==0){
System.out.println(prefix);
System.out.println(count+"at print");
} else{
for(int i=0;i<str.length();i++){
String rem=str.substring(0,i)+str.substring(i+1);
permutation(rem,prefix+str.charAt(i));
System.out.println(count);
count= count+1;
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.