# 回溯算法相关题目

回溯算法是一种基于递归的暴力搜索算法。它适用于排列、组合、切割、子集和棋盘类型的问题。

public void backTracking([[prameter list]]) {
    if ([[ternimate condition]]) {
        // result collection
    }

    for (ele : collection of current level) {
        // elements process
        // recursion
        // back tracking
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 组合

# 组合 LeetCode 77

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1
输入:n = 4, k = 2
输出:

[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
1
2
3
4
5
6
7
8

示例 2
输入:n = 1, k = 1
输出:[[1]]

解法

class Solution {
    List result = new ArrayList();
    List path = new ArrayList();

    // 数字集合 1-n, 取 k 个数进行组合, 每次判断的起始位是 startIndex
    public void backTracking(int n, int k, int startIndex) {
        // 终止条件
        if (path.size() == k) {                 // 树的深度就是要取的组合位数
            result.add(new ArrayList(path));    // 把当前路径作为其中一个结果放进结果集
            return;
        }

        // 横向处理本层
        for (int i = startIndex; i <= n; i++) { // 从 startIndex 到结尾,每个都取一次生成新树
            // 处理结点
            path.add(i);                    // 取当前数字作为一个路径加入 path
            // 递归
            backTracking(n, k, i + 1);      // 从下一个数字开始作为下一层重复
            // 回溯撤回
            path.remove(path.size() - 1);   // 回溯,去掉当前数字,处理本层第二个元素
        }
    }

    public List<List<Integer>> combine(int n, int k) {
        backTracking(n, k, 1);
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

改进
[分析]
当在横向处理时,剩余元素个数不足以组合成 k 个数字时,则可以提前停下。即生成新树有前提条件,元素个数不满足的可以不用考虑。所以可以对循环条件修改来减少遍历元素个数。
所有元素数量: n 当前处理过的元素数量: path.size()
单个结果需要总元素: k
单个结果需要的剩余元素数量: k - path.size() 符合条件的最后的需要遍历开始位置: n - (k - path.size()) + 1

例如 n = 5, k = 3 时。如果当前还没有遍历元素,则:5 - (3 - 0) + 1 = 3.
即从 3 开始遍历是最后一个可以被接受的组合,如 [3 4 5],而 4 开头和 5 开头都因为个数不够而剪枝。
剪枝操作在当前选择个数小时最有效,当已选择的个数多时,结果一般都会为完全遍历。

class Solution {
    List result = new ArrayList();
    List path = new ArrayList();

    // 数字集合 1-n, 取 k 个数进行组合, 每次判断的起始位是 startIndex
    public void backTracking(int n, int k, int startIndex) {
        // 终止条件
        if (path.size() == k) {                 // 树的深度就是要取的组合位数
            result.add(new ArrayList(path));    // 把当前路径作为其中一个结果放进结果集
            return;
        }

        // 横向处理本层
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 从 startIndex 到合适的结尾,每个都取一次
            // 处理结点
            path.add(i);                    // 取当前数字作为一个路径加入 path
            // 递归
            backTracking(n, k, i + 1);      // 从下一个数字开始作为下一层重复
            // 回溯撤回
            path.remove(path.size() - 1);   // 回溯,去掉当前数字,处理本层第二个元素
        }
    }

    public List<List<Integer>> combine(int n, int k) {
        backTracking(n, k, 1);
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28