递归和回溯解决括号匹配问题

递归和回溯解决括号匹配问题

递归在之前的文章中有提及,有朋友在后台大呼不过瘾,刚好又刷到了一道可以用到递归的算法题,还是我们的老朋友:匹配有效括号,废话不多说,先上题目。

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。   示例: 输入:n = 3 输出:[ "((()))", "(()())", "(())()", "()(())", "()()()" ] 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/generate-parentheses

经过分析,这涉及到了两个关键问题:

如何判断组合成的括号字符串是有效的

如何构建所有可能性的括号字符串。

第一个问题比较容易想通,必须要左右括号成对出现即可。
如果是暴力解法可以考虑把这两个问题分成两步去处理好,而如果是用递归,肯定是希望每次递归的时候不仅创建出排列组合,且这个组合出来的字符串是有效的。

func generateParenthesis(n int) []string { return generate(n) } func generate(n int) []string { cache := map[int][]string{} if cache[n] != nil { return cache[n] } var ans []string if n == 0 { ans = append(ans, "") } else { for c := 0; c < n; c++ { currentLeft := generate(c) currentRight := generate(n - c -1) for _, value_left := range currentLeft { for _, value_right := range currentRight { ans = append(ans, "(" + value_left + ")" + value_right) } } } } cache[n] = ans return ans }

递归的关键在于找到边界,也就是分别对左括号和右括号的创建进行递归,这样就能生成出符合条件的字符串。总体来说这次递归的解法非常符合人性,条理清晰,也用到一个cache的map来存储结果,提高执行效率。

其实这道题也可以使用回溯的方法来实现,具体代码实现如下:

func generateParenthesis2(n int) []string { ans := new([]string) // 设置一个指向类型为string的切片的指针 var curr []byte backtrack(ans, curr, 0, 0, n) return *ans // 反取指针,获得结果切片 } // 回溯函数 func backtrack(ans *[]string, curr []byte, open int, close int, max int) { if len(curr) == max * 2 { *ans = append(*ans, string(curr[:])) return } if open < max { // 左括号没有超过最大值,则添加做括号,最后删除切片的最后一个元素 curr = append(curr, '(') backtrack(ans, curr, open+1, close, max) curr = append(curr[0:len(curr)-1], curr[len(curr):]...) // 这个删除方式最直接,但是 } if close < open { // 当右括号比作括号少,则添加,最后删除切片的最后一个元素 curr = append(curr, ')') backtrack(ans, curr, open, close+1, max) curr = append(curr[0:len(curr)-1], curr[len(curr):]...) } }

回溯也就是动态规划,把每次结果都带入到下一次回溯中,通过比较开闭括号的个数,来动态地调整。
算法题有时候可以考虑多种解法去实现,刷题的同时也能体会到不同编程语言的妙处。
本文首发自我的微信公众号:成都有娃儿,欢迎关注。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxgpd.html