繁体   English   中英

理解示例 12 来自大 O 符号的字符串的所有排列 - 破解编码面试

[英]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))

解决这个重复问题并不是那么容易,如果我没记错的话,它应该归结为解决:

foo+bar

但让我们尝试近似。 每个排列(基本情况)代表递归树中的一个节点。 这棵树有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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM