Java 包含递归调用的循环函数的运行时

Java 包含递归调用的循环函数的运行时,java,time-complexity,Java,Time Complexity,我正在解决以下问题: 查找以下代码的时间复杂度: 我的思考过程如下: 查看对排列的每次调用中所做的工作: System.out.println(前缀)将花费O(n)时间 String rem=str.substring(0,i)+str.substring(i+1)也需要O(n)时间。这是因为合并两个字符串需要O(m+n)时间,其中m和n是字符串长度。因为在这种情况下,长度加起来总是n,所以总共是O(n) 最后,对permutation(还不是递归)和str.charAt(i)语句的实际调用将

我正在解决以下问题:

查找以下代码的时间复杂度:

我的思考过程如下:

查看对
排列的每次调用中所做的工作:

System.out.println(前缀)将花费O(n)时间

String rem=str.substring(0,i)+str.substring(i+1)也需要O(n)时间。这是因为合并两个字符串需要O(m+n)时间,其中m和n是字符串长度。因为在这种情况下,长度加起来总是n,所以总共是O(n)

最后,对
permutation
(还不是递归)和
str.charAt(i)
语句的实际调用将在常量时间so(1)中完成

然而,我感到困惑的是,这是在一个循环中完成的。在我的书中,这个解决方案被解释为“调用树中的每个节点都对应于O(n)功”。每个节点不是都需要O(n+n^2)=0(n^2)工作吗?因为循环的每个迭代都需要O(n)工作,并且会被调用n次

因此,在考虑了几次之后,我决定更笼统地重新表述我的问题,你将如何以标准化的方式确定这种性质的函数的时间复杂性(一种可以应用于大多数(如果不是所有)这种性质的问题的方法)?虽然只是通过情况进行推理是可行的,但也容易产生误解(正如我所证明的),那么有没有更一般的方法来解决这个问题呢


好吧,在反复思考这个问题之后,我意识到我的想法是正确的,但只是关于实际解决方案本身是如何构建的。我发现你可以从两个方面考虑这种情况,记住以下几点

因此,首先,在处理这个场景时,我们首先查看对
置换的每个调用中的工作:

  • System.out.println(前缀)-->O(n)
  • String rem=str.substring(0,i)+str.substring(i+1)-->O(n)
  • 置换(rem,前缀+str.charAt(i))-->O(1)[这还没有考虑递归!!!]
现在我们知道了整个调用树中每个节点内部完成的工作量。现在我们考虑递归。对此有两种不同的思考方式

1) 递归与循环相结合 由于循环和递归调用都会导致调用
permutation
,因此我们只需担心对
permutation
的调用总数,而不必区分循环调用和常规递归调用。因此,我们以一个字符串为例,假设为“abc”,并检查调用树的叶节点:

  • “abc”-->1个节点
  • “a”、“b”、“c”-->3个节点
  • “ab”、“ac”、“ba”、“bc”、“ca”、“cb”-->6个节点
  • 因此,我们看到有6个叶节点。我们可以立即看到这等于3!其中3是输入字符串“abc”的长度。因此,叶节点的数量将是
    n其中
    n
    对应于输入字符串长度。调用树的深度始终为
    n
    ,因为递归在
    n=0时停止。因此,调用树中的节点总数将是
    n*n。因为每个节点对应于
    O(n)
    功,所以我们完成的总功是
    O(nxnnxn!)=O((n+2)!)
    [我们可以这样做,因为我们可以删除常量!]

    2) 与循环分离的递归 现在,这就是我的困惑和这个问题的根源。在每个节点中完成的工作应该是
    O(n^2)
    而不是
    O(n)
    ,这似乎是合乎逻辑的,因为
    for
    循环在节点内部,对吗?在本例中,我认为for循环和递归是独立的实体。但是,这种思维方式并不真正正确,因为
    for
    循环不只是每次被调用时运行
    n
    次,而是运行
    n
    次,然后运行
    n-1
    次,然后运行
    n-2
    ,等等。总之,它运行
    n每次启动的次数。因此,我们有
    n运行,每次执行
    O(n)
    工作意味着启动后总共运行
    O(n x n!)
    次。但是,现在我们只考虑了每次调用
    for
    循环的次数。它实际启动了多少次?这将是
    n
    次,因为我们可以看到递归在
    n=0
    时结束。因此,
    nxnnxn再一次

    现在,如果你发现了我推理中的错误(特别是第二种思维方式),请毫不犹豫地指出它!我想确保我正确地思考了这个问题


    好吧,在反复思考这个问题之后,我意识到我的想法是正确的,但只是关于实际解决方案本身是如何构建的。我发现你可以从两个方面考虑这种情况,记住以下几点

    因此,首先,在处理这个场景时,我们首先查看对
    置换的每个调用中的工作:

    • System.out.println(前缀)-->O(n)
    • String rem=str.substring(0,i)+str.substring(i+1)-->O(n)
    • 置换(rem,前缀+str.charAt(i))-->O(1)[这还没有考虑递归!!!]
    现在我们知道了整个调用树中每个节点内部完成的工作量。现在我们考虑递归。对此有两种不同的思考方式

    1) 递归与循环相结合 由于循环和递归调用都会导致调用
    permutation
    ,因此我们只需担心对
    permutation
    的调用总数,而不必区分循环调用和常规递归调用。因此,我们以一个字符串为例,假设为“abc”,并检查调用树的叶节点:

  • “abc”