Algorithm 这个算法的空间复杂度是多少?

Algorithm 这个算法的空间复杂度是多少?,algorithm,recursion,time-complexity,space-complexity,catalan,Algorithm,Recursion,Time Complexity,Space Complexity,Catalan,这是破解编码面试的问题9.6(第5版) 实现一个算法来打印n对括号的所有有效组合 范例 输入:3 输出:(())、(())、(())()、(())、(())、(())、(()) 下面是我实现的算法(用Java) 私有静态集getAllComb(int n){ Set allPoss=new HashSet(); 如果(n>0){ 如果(n==1){ 全部可能添加(“()”); }否则{ Set before=getAllComb(n-1); for(字符串短语:before){ int leng

这是破解编码面试的问题9.6(第5版)

实现一个算法来打印n对括号的所有有效组合
范例
输入:3
输出:(())、(())、(())()、(())、(())、(())、(())

下面是我实现的算法(用Java)

私有静态集getAllComb(int n){
Set allPoss=new HashSet();
如果(n>0){
如果(n==1){
全部可能添加(“()”);
}否则{
Set before=getAllComb(n-1);
for(字符串短语:before){
int length=短语.length();
对于(int start=length-2;start>=0;start--){
如果(短语.charAt(开始)='('){
String phraseToConsider=短语.子字符串(0,开始+1)+“()”+
短语.子字符串(开始+1);
如果(!allPoss.contains(phraseToConsider)){
所有可能添加(措辞考虑);
}
}
}
字符串短语toconsider=“()”+短语子字符串(0);
如果(!allPoss.contains(phraseToConsider)){
所有可能添加(措辞考虑);
}
}
}
}
返回allpos;
}
这会产生正确的输出。我知道面试官(至少在亚马逊)喜欢问你解决方案的时间和空间复杂性。对于时间复杂性,我能够证明算法在O(n)中运行使用递归关系。我在分析空间复杂性方面遇到困难。这是一个递归解决方案,因此它至少应该是O(n),但在每次递归调用时,我也生成一个以n为界的集。由于n个递归调用,总空间是O(n)还是O(n2)因为为每个递归调用设置了绑定n的大小n

对于时间复杂度,我能够证明该算法在O(n)中运行,并且具有递归关系

这是错误的。平衡圆括号的序列数由以下公式给出:此类序列的数量是指数级的。如果您的算法也能正确地解决问题,那么它就不可能是线性的,因为仅仅输出指数级的解本身就需要指数级的时间

至于内存复杂性,您似乎在递归的每个步骤中存储了
n-1
的所有解决方案,因此内存复杂性在我看来也是指数级的,加上您在每个步骤中创建的其他字符串和递归调用,这只会增加复杂性


不使用指数内存就可以解决这个问题:考虑一下如何摆脱存储所有以前的序列。

写入n对正确匹配的括号的方法是第n个加泰罗尼亚数,它实际上是指数增长的,而不是平方增长的。仅输出的空间复杂度就为0(2^n);有关加泰罗尼亚数字的快速概述,请参阅


请注意,您并不是在每个深度进行一次递归调用,而是可能进行O(n)次递归调用。

这方面的一些技巧与您的问题没有直接关系:1)您应该能够使用n=0作为基本情况;递归问题中的常见问题是不需要特别处理n=1 2)如果使用长度为2n的缓冲区,递归调用“fill-in”,则应该能够将空间复杂度降低到O(n)。对于n=4,有14种可能的方法可以正确地编写匹配的论文。长度为2n(或kn表示任意k)的缓冲区太小,通常无法保存输出。如何使用长度为2n的缓冲区?你不能预先定义集合的大小。@HugomgI只是希望有另一个N=1的基本情况,因为这只是添加“()”的一行代码。您不必遍历所有代码行来迭代上一个集合、所有字符串等。这真的是一个糟糕的设计吗?它本身并不坏,但额外的代码行意味着有更多的地方可以隐藏bug:)并且通常使用相同的尾码分支来处理N=1,而不是其他分支,这不会对性能造成很大影响。如果是,那么就有可疑的事情发生了……你不需要存储所有以前的序列来应用算法吗?如果不存储它们,就无法访问所有字符串并形成所有括号组合。@CommittedDroider-在每一步中,都有两种可能:放一个开括号或一个闭括号。通过这样做,您可以将内存使用保持在
O(n)
。这也会生成不平衡的序列,但如果您知道如何使它只生成平衡的序列,那么您就完成了。提示:记下你开了多少,关了多少。我不太明白你的意思。我是如何看待这个问题的,是如何找到在左括号前后加“()”的可能性。假设我有第一个函数调用f(1),它是()。在这种情况下,你说的每一步是什么意思,放一个开放的括号还是封闭的括号?您不需要同时放一个开放括号和一个封闭括号吗?@CommittedDroider思考如何通过从左到右放置
来构建解决方案:在每一步中,您必须有更多的开放而不是封闭。然后你可以回溯。打开
n
后,必须关闭其余部分。如果可以的话,一定要打开门。例如,您从
(())
开始。然后你回溯到
((
)()。你可以将最后一个设置为关闭的:
(())(
。然后,下一个可以是打开的:
(())(
。现在你只能将其余的设置为关闭:
(())
等等。通过你所做的,我看到了从((())到(())的过程,但你如何从((())到(())呢?您会首先将其回溯到什么?您所说的“注意,您并没有在e上进行一次递归调用”是什么意思
private static Set<String> getAllComb(int n) {
      Set<String> allPoss = new HashSet<String>();
      if(n>0) {
          if(n==1) {
              allPoss.add("()");
          } else {
              Set<String> before = getAllComb(n-1);
              for(String phrase: before) {
                  int length = phrase.length();
                  for(int start = length - 2; start>=0; start--) {
                      if(phrase.charAt(start) == '(') {
                          String phraseToConsider = phrase.substring(0, start+1) + "()" +
                               phrase.substring(start + 1);
                          if(!allPoss.contains(phraseToConsider)){
                              allPoss.add(phraseToConsider);
                          }
                      }
                  }
                  String phraseToConsider = "()" + phrase.substring(0);
                  if(!allPoss.contains(phraseToConsider)){
                      allPoss.add(phraseToConsider);
                  }
              }
          }
      }
      return allPoss;
}