【算法总结】——组合型回溯

文章目录

  • 组合型回溯
    • 例题1——组合
      • 从输入考虑模板
      • 从答案考虑模板
    • 例题2——括号生成
      • 解法一
      • 解法二
  • 剪枝
  • 分析回溯时间复杂度的通用方法

组合型回溯

组合型和子集型之间的差异在哪里呢?
【算法总结】——组合型回溯_第1张图片

相比子集问题,组合问题是可以做一些额外的优化的(因为只需要得到某些特定的子集)。


同样是 【选和不选】 以及 【枚举选哪个】 这两种思路。
(具体用哪种思路根据题目特点来做,哪种更好些就选哪种写法)

例题1——组合

https://leetcode.cn/problems/combinations/
【算法总结】——组合型回溯_第2张图片

从输入考虑模板

依然是考虑每个数字选或不选,与子集型回溯的差异在于,结果的元素数量必须是k。

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

    public List<List<Integer>> combine(int n, int k) {
        dfs(1, n, k);
        return ans;
    }

    public void dfs(int i, int n, int k) {
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        if (i > n) return;
        dfs(i + 1, n, k);
        t.add(i);
        dfs(i + 1, n, k - 1);
        t.remove(t.size() - 1);
    }
}

从答案考虑模板

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

    public List<List<Integer>> combine(int n, int k) {
        dfs(1, n, k);
        return ans;
    }

    public void dfs(int startIndex, int n, int k) {
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        for (int i = startIndex; i <= n; ++i) {
            t.add(i);
            dfs(i + 1, n, k - 1);
            t.remove(t.size() - 1);
        }
    }
}

例题2——括号生成

解法一

选左括号还是右括号

class Solution {
    List<String> ans = new ArrayList();
    StringBuilder t = new StringBuilder();

    public List<String> generateParenthesis(int n) {
        dfs(n, n);
        return ans;
    }

    public void dfs(int n1, int n2) {
        if (n1 == 0 && n2 == 0) {
            ans.add(t.toString());
            return;
        }
        if (n1 > 0) {   // 左括号
            t.append('(');
            dfs(n1 - 1, n2);
            t.deleteCharAt(t.length() - 1);
        }
        if (n2 > n1) {  // 右括号
            t.append(')');
            dfs(n1, n2 - 1);
            t.deleteCharAt(t.length() - 1);
        }
    }
}

其实回溯里的 if 就相当于是在做剪枝了,所谓的剪枝就是提前过滤掉不合理的答案。

解法二

枚举左括号出现的位置

class Solution {
    List<String> ans = new ArrayList();
    List<Integer> pos = new LinkedList();   // 存储左括号的位置

    public List<String> generateParenthesis(int n) {
        dfs(0, 0, n);
        return ans;
    }

    public void dfs(int startIndex, int cnt, int n) {
        if (cnt == n) {
            char[] chs = new char[2 * n];
            Arrays.fill(chs, ')');
            for (int idx: pos) chs[idx] = '(';
            ans.add(new String(chs));
            return;
        }
        for (int i = startIndex; i <= 2 * cnt; ++i) {
            pos.add(i);
            dfs(i + 1, cnt + 1, n);
            pos.remove(pos.size() - 1);
        }
    }
}

这里 for 循环中的 i <= 2 * cnt 相当于剪枝。

剪枝

这里以解法一为例介绍剪枝操作。
由于答案中必须有 k 个元素,因此如果选到 i 的时候,还有 n - i + 1 个元素可供选择,因此如果可供选择的数字已经少于还需要选择的元素,那么就可以不再继续往下尝试了。

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

    public List<List<Integer>> combine(int n, int k) {
        dfs(1, n, k);
        return ans;
    }

    public void dfs(int i, int n, int k) {
        if (n - i + 1 < k) return;	// 剪枝
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        dfs(i + 1, n, k);
        t.add(i);
        dfs(i + 1, n, k - 1);
        t.remove(t.size() - 1);
    }
}

对于模板二的剪枝方式为:

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

    public List<List<Integer>> combine(int n, int k) {
        dfs(1, n, k);
        return ans;
    }

    public void dfs(int startIndex, int n, int k) {
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        for (int i = startIndex; i <= n - k + 1; ++i) {		// 剪枝 i <= n - k + 1
            t.add(i);
            dfs(i + 1, n, k - 1);
            t.remove(t.size() - 1);
        }
    }
}

分析回溯时间复杂度的通用方法

时间复杂度是:**叶子的个数** 乘上 **从根到叶子的路径长度**
对于这道题目(例题一)来说,就是 k * C(n, k)

【算法总结】——组合型回溯_第3张图片

你可能感兴趣的:(算法,算法,回溯,组合)