递归与回溯1:生成全部有效括号组合

从现在开始我们一起来梳理递归相关的问题。递归是高阶算法的核心,回溯、dp都是递归的拓展和延伸。

废话少说,递归的问题一般我们不要急着看代码,因为看了也不懂,而应该先分析问题的本质,从小到大逐步归纳出特征,最后再写代码。而计算机调用的时候恰好是从大小到逐步递进的。

先看一个CC150中的题目,实现一种算法,打印n对括号中的全部有效组合,即左右括号正确配对。

示例:输入n=3,输出:((())), (()()), (())(), ()(()),()()()所以有3个。

这个题如果用递归的话怎么做呢?

如果直接想是不好想的,我们画一个图看一下:

很明显看到, 从n=2开始就是将上面一层的每一个都是在其左边、右边和包起来三种操作,n从1到2就很明显,从n到3就是图中红色标记的地方。后面的每一层都对前面做相同的三个操作。这就是一个从小到大不断递归的过程。

但是这里有个问题,出现重复了怎么办?例如n=2的时候()()和()()是一样的。显然这时候只保留一种就足够了,后序递归操作也只处理一个就行了,那如何去重呢?我们想到集合能保证元素唯一,所以我们可以将每层的元素用集合来保存。代码如下:

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<String>();
        
        backtrack(ans, new StringBuilder(), 0, 0, n);
        return ans;
    }
    
    /**
     * @param ans 当前递归得到的结果
     * @param cur 当前的括号串
     * @param open   左括号已经使用的个数
     * @param close  右括号已经使用的个数
     * @param max    序列长度最大值
     */
    public void backtrack(List<String> ans, StringBuilder cur, int open, int close, int max) {
        if (cur.length() == max * 2) {
            ans.add(cur.toString());
            return;
        }
        //本题需要两次回溯,比较少见的情况
        if (open < max) {
            cur.append(();
            backtrack(ans, cur, open + 1, close, max);
            cur.deleteCharAt(cur.length() - 1);
        }
        if (close < open) {
            cur.append());
            backtrack(ans, cur, open, close + 1, max);
            cur.deleteCharAt(cur.length() - 1);
        }
    }
}

这里虽然有个for循环,但是只是简单的拼接字符串,因此难度不算很大的。

经验分享 程序员 微信小程序 职场和发展