打印n对括号的所有有效组合的算法

时间:2017-01-10 01:56:10

标签: ruby algorithm recursion time-complexity space-complexity

我正在处理问题陈述中陈述的问题。我知道我的解决方案是正确的(运行程序),但我很好奇我是否正确分析了我的代码(下面)。

def parens(num)
  return ["()"] if num == 1
  paren_arr = []
  parens(num-1).each do |paren| 
    paren_arr << paren + "()" unless "()#{paren}" == "#{paren}()"
    paren_arr << "()#{paren}"
    paren_arr << "(#{paren})"
  end
  paren_arr
end
例如,parens(3)将输出以下内容:

["()()()", "(()())", "(())()", "()(())", "((()))"]

以下是我的分析: 每个f(n)值大约是f(n-1)的3倍。所以:

f(n)= 3 * f(n-1)= 3 * 3 * f(n-2)〜(3 ^ n)时间成本。 通过类似的分析,堆栈将被f(1)... f(n)占用,因此空间复杂度应为3 ^ n。

我不确定这种时间或空间分析是否正确。一方面,堆栈只保存n个函数调用,但这些调用中的每一个都返回一个数组〜之前调用的3倍。这会影响太空成本吗?我的时间分析是否正确?

3 个答案:

答案 0 :(得分:6)

正如其他人所说,你的解决方案不正确。

我最喜欢的解决方案是通过反复将当前字符串递增到词法上的下一个有效组合来生成所有有效组合。

“Lexually next”分解为一些规则,使其变得非常简单:

  • 字符串中的第一个区别是'('变为'')'。否则,下一个字符串将在当前字符串之前有词法。

  • 第一个区别是尽可能向右。否则会有较小的增量。

  • 第一个差异之后的部分在词汇上是最小的,再次因为否则会有较小的增量。在这种情况下,这意味着所有'('在所有'之前')'。

所以你要做的就是找到最右边的'('可以改为')',翻转它,然后追加正确数量的'('s和')。

我不认识Ruby,但在Python中看起来像这样:

current="(((())))"
while True:
    print(current)
    opens=0
    closes=0
    pos=0
    for i in range(len(current)-1,-1,-1):
        if current[i]==')':
            closes+=1
        else:
            opens+=1
            if closes > opens:
                pos=i
                break
    if pos<1:
        break
    current = current[:pos]+ ")" + "("*opens + ")"*(closes-1)

输出:

(((())))
((()()))
((())())
((()))()
(()(()))
(()()())
(()())()
(())(())
(())()()
()((()))
()(()())
()(())()
()()(())
()()()()

对于许多类型的“生成所有组合”问题,这样的解决方案变得简单快捷。

答案 1 :(得分:2)

递归推理是一个简单的解决方案。如果剩余要发射的左侧parens的数量是正数,则发出一个并重复。如果剩余发射的右侧数量大于左侧,发射和重复的数量。基本情况是所有的左右两个parens都被发射出来。打印。

def parens(l, r = l, s = "")
  if l > 0 then parens(l - 1, r, s + "(") end
  if r > l then parens(l, r - 1, s + ")") end
  if l + r == 0 then print "#{s}\n" end
end

正如其他人所说,加泰罗尼亚数字给出了将要打印的字符串数量。

虽然这个Ruby实现没有实现它,但是较低级别的语言(如C)可以很容易地使用单个字符串缓冲区:O(n)空间。由于子串复制,这个是O(n ^ 2)。但由于运行时间和输出长度为O(n!),因此O(n)空间效率低并不大。

答案 2 :(得分:1)

我找到了汤姆戴维斯&#39; article,&#34;加泰罗尼亚数字,&#34;非常有助于解释一种定义Catalan Numbers的递归方法。我会尝试自己解释(部分地,看看我已经理解了多少),因为它可以应用于找到BROWSERNAME匹配括号的所有独特排列的集合(例如,1(); 2()(),(());等)。

对于NN > 1代表(A)B个匹配括号的一种排列,其中NA各自只有平衡的括号。然后我们知道如果B包含A个匹配集,则k必须包含其他B,其中N - k - 1

在以下示例中,点表示该组具有零组括号:

0 <= k <= N - 1

要枚举C_0 => . C_1 => (.) ,我们会在所有方面安排C_2作为C_1,并将第二个括号放在AB周围:

A

现在. () = AB = C_0C_1 => (.)() () . = AB = C_1C_0 => (()) . C_3有三个分区,每个分区都有自己的组合:N - 1

C_0C_2, C_1C_1, C_2C_0

我们可以通过为每个C_0C_2 = AB = . ()() and . (()) => ()()(), ()(()) C_1C_1 = AB = ()() => (())() C_2C_0 = AB = ()() . and (()) . => (()()), ((())) 保留一个集合并迭代每个分区的组合来编写此方法。我们将各个排列保持为位:左侧为0,右侧为1(当作为二进制字符串转换时,它会向后显示)。

N

yielder是懒惰的:虽然列表是无限的,但我们可以尽可能少地生成(例如使用.take):

def catalan
  Enumerator.new do |y|
    # the zero here represents none rather than left
    s = [[0],[2]]
    y << [0]
    y << [2]
    i = 2

    while true
      s[i] = []

      (0..i - 1).each do |k|
        as = s[k]
        bs = s[i - k - 1]

        as.each do |a|
          bs.each do |b|
            if a != 0
               s[i] << ((b << (2*k + 2)) | (1 << (2*k + 1)) | (a << 1)) 
            else
               s[i] << (2 | (b << 2))
            end

          end # bs
        end # as

      end # k

      y.yield(s[i])

      i = i + 1
    end # i

  end # enumerator
end

catalan.take(4) 
# => [[0], [2], [10, 12], [42, 50, 44, 52, 56]]

前一代人要求我们保留所有先前的套装,以便发行下一套。或者,我们可以通过更有机的类型,蜿蜒的递归来构建请求的集合。下一个版本产生了块的每个排列,因此我们可以输入:

catalan.take(4).last.map{|x| x.to_s(2)} 
# => ["101010", "110010", "101100", "110100", "111000"]

直接生成:

catalan(4){
  |x| (0..7).reduce(""){
    |y,i| if x[i] == 0 then y + "(" else y + ")" end
  }
}.take(14)

# => ["(((())))", "((()()))", "((())())", "((()))()", "(()(()))", "(()()())",
#     "(()())()", "(())(())", "(())()()", "()((()))", "()(()())", "()(())()",
#     "()()(())", "()()()()"]