Arithmetic-回溯法

341 阅读3分钟

题目

给出 n 代表生成的括号对数,请你写出一个函数,使其能生成所有的可能的并且 有效的括号组合

算法

如果完成一件事情有多种路径,且每种路径分成若干步骤,多半可以采用"回溯"法:

  • 基本思路是:"尝试搜索",一条路走不通,就返回上一节点,走另一条路
  • 如果提前分析出,走该条路无效,可以跳过该分支,称"剪枝"

思路

  1. 画树形结构图,分析递归结构
  2. 递归的终点,是否可以剪枝
  3. 遍历: 深度优先,广度优先

代码

public class GeneParen {

深度优先

//<editor-fold desc="深度优先遍历">
    @Test
    public void generateParenthesis1() {

        int n = NUMBER;
        //存放结果集的List
        List<String> results = new ArrayList<>();

        if (n == 0) {
            return;
        }
        //执行深度优先遍历
        dfs("", n, n, results);
        System.out.println(results);
    }

    private void dfs(String curStr, int left, int right, List<String> results) {
        if (left == 0 && right == 0) {
            results.add(curStr);
            return;
        }

        //深度优先遍历 - 先左边
        if (left > 0) {
            dfs(curStr + "(", left - 1, right, results);
        }
        //然后再深度遍历 - 右边
        if (right > 0 && right > left) {
            dfs(curStr + ")", left, right - 1, results);
        }
    }
    //</editor-fold>

广度优先

@Test
    public void generateParenthesis2() {

        int n = NUMBER;
        if (n == 0) {
            return;
        }
        //执行广度优先遍历
        System.out.println(dfa(n));
    }

    /*
     * 定义一个Node类,存放节点的 '左','右'括号数 和 '当前节点字符串' */
    static class Node {
        private int left;
        private int right;
        private String res;

        Node(int left, int right, String res) {
            this.left = left;
            this.right = right;
            this.res = res;
        }
    }

    /**
     * @LinkedList 非阻塞队列
     * offer() 添加一个元素到队尾并返回true   如果队列已满,则返回false
     * poll()  '移除'并'返回'队列头部的元素   如果队列为空,则返回null
     */
    private List<String> dfa(int n) {
        //存放结果集
        List<String> results = new ArrayList<>();
        if (n == 0) {
            return results;
        }
        //存放节点列的Queue
        Queue<Node> queue = new LinkedList<>();
        //放入根节点
        queue.offer(new Node(n, n, ""));

        //每次while循环: 接上一个 '(' 或 ')' , n对共2n次
        int m = 2 * n;
        while (m > 0) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {

                //取出最新节点
                Node curNode = queue.poll();
                assert curNode != null;
                if (curNode.left > 0) {
                    //若满足条件,向当前节点下 接入 左子节点 -> 节点的left-1,res接左括号
                    queue.offer(new Node(curNode.left - 1, curNode.right, curNode.res + "("));
                }
                if (curNode.right > 0 && curNode.left < curNode.right) {
                    //若满足条件,向当前节点 接入 右子节点 -> 节点的right-1,res接右括号
                    queue.offer(new Node(curNode.left, curNode.right - 1, curNode.res + ")"));
                }
            }
            m--;
        }
        //节点列存放结束后,将节点的res存放到结果集中
        while (!queue.isEmpty()) {
            results.add(queue.poll().res);
        }
        return results;
    }
    //</editor-fold>

动态规划(状态转移)

dp[i] = "(" + dp[可能的括号数] + ")" + dp[剩下的括号数]

可能的括号数: j: 0 -> i - 1;

剩下的括号数: i - j - 1;

//<editor-fold desc="动态规划">
    @Test
    public void generateParenthesis3() {

        int n = NUMBER;
        if (n == 0) {
            return;
        }
        System.out.println(dys(n));
    }

    private List<String> dys(int n) {
        if (n == 0) {
            return new ArrayList<>();
        }

        //初始化, dp[0]中存放 空字符串,算上空字符串,共n + 1 个
        List<String>[] dp = new ArrayList[n + 1];
        List<String> dp0 = new ArrayList<>();
        dp0.add(""); //不能直接赋值给dp[0],因为add的返回值是boolean
        dp[0] = dp0;

        for (int i = 1; i <= n; i++) {

            List<String> together = new ArrayList<>();

            for (int j = 0; j < i; j++) {

                List<String> str1 = dp[j];
                List<String> str2 = dp[i - j - 1];

                for(String s1 : str1) {
                    for (String s2 : str2) {
                        together.add("(" + s1 + ")" + s2);
                    }
                }
            }
            dp[i] = together;
        }
        //dp[1 -> n]中存放了n为任意值时 括号的情况
        return dp[n];
    }
    //</editor-fold>
}