给定n=3,如何生成[’((()))’, ‘(()())’, ‘(())()’, ‘()(())’, ‘()()()’],直接附上代码(python版)
class Solution(object):
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
res=[]
def back(left, right, part):
if left == right and left == n:
res.append(part)
return
if left<n: #第①处
back(left + 1, right, part + '(')
if right<left: #第②处
back(left, right + 1, part + ')')
back(0, 0, "")
return res
s=Solution()
print(s.generateParenthesis(3))
递归代码非常简洁,以至于你会怀疑人生,如此简单的代码为啥就能达到实际效果?
关键就在于①和②一前一后的巧妙设计,其中两个if条件是相互独立的,即刚开始①满足而②不满足,后来①和②都满足,再后来①不满足而②满足。两个back子程序交替执行,因此虽然只有两句话,但递归调用后,就会衍生出很多分支,其中每个分支计算一个括号对。
下面利用最原始的方式(类似单步调试)来理解这个程序,看看结果是如何得到的
嵌套层次 | 该层要做的事 |
---|---|
第0层 | back(0, 0, “”) |
第1层 | back(1, 0, “(”) |
第2层 | back(2, 0, “((”) ; back(1, 1, “()”) |
第3层 | back(3, 0, “(((”) ; back(2, 1, “(()”);back(2, 1, “()(”) |
第4层 | back(3, 1, “((()”) ; back(3, 1, “(()(”);back(2, 2, “(())”);back(3, 1, “()((”);back(2, 2, “()()”) |
第5层 | back(3, 2, “((())”) ; back(3, 2, “(()()”);back(3, 2, “(())(”);back(3, 2, “()(()”);back(3, 2, “()()(”) |
第6层 | back(3, 3, “((()))”) ; back(3, 3, “(()())”);back(3, 3, “(())()”);back(3, 3, “()(())”);back(3, 3, “()()()”) |
第7层 | append+return; append+return;append+return;append+return;append+return |
可以看到递归其实就是压栈和弹栈,栈是一种先进后出的数据结构。这里嵌套层次低的是先进的,嵌套层次高的是先出的。嵌套层次低的依赖嵌套层次高的先解决,才能求解。嵌套层次最高的(第7层)直接返回结果。
事实上,嵌套层次低的back函数求解转化为嵌套层次高的back函数求解(表格中应该用箭头标出)。这样看来,递归也可以用树来描述。其中树根是原始问题,内部节点是问题的转化和求解过程,叶子节点是问题的求解结果。
那么栈和树有什么联系?树根可以看成栈底(表示原始问题),叶子节点看成栈顶(表示能够直接求解的子问题)。树中同一层的节点属于同一个栈元素。纯属个人的一种形象理解 : )
学习递归,我认为有两个阶段,一是能理解递归的执行过程,二是能自己写递归解决问题。本文关注的是第一阶段。