題目描述
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
思路:
回溯剪枝法
代码:
public class Solution {
private List<List<Integer>> res = new ArrayList<>();
private void findCombinations(int n, int k, int begin, Stack<Integer> pre) {
if (pre.size() == k) {
// 够数了,就添加到结果集中
res.add(new ArrayList<>(pre));
return;
}
// 关键在于分析出 i 的上界
for (int i = begin; i <= n; i++) {
pre.add(i);
findCombinations(n, k, i + 1, pre);
pre.pop();
}
}
public List<List<Integer>> combine(int n, int k) {
// 特判
if (n <= 0 || k <= 0 || n < k) {
return res;
}
// 从 1 开始是题目的设定
findCombinations(n, k, 1, new Stack<>());
return res;
}
public static void main(String[] args) {
Solution solution = new Solution();
List<List<Integer>> combine = solution.combine(4, 2);
System.out.println(combine);
}
}
这个方法,我们遗留了一个问题,那就是我们感觉有些分支没有必要执行,那就是每一层最后要执行的那些分支,下面我们具体研究一下这个问题。
其中绿色的部分,是不能产生结果的分支,但是我们的代码确实又执行到了这部分,所以应该去掉,我们的剪枝过程就是:把 i <= n 改成 i <= n - (k - pre.size()) + 1
优化后的代码:
public class Solution3 {
private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
if (n <= 0 || k <= 0 || n < k) {
return result;
}
findCombinations(n, k, 1, new Stack<>());
return result;
}
private void findCombinations(int n, int k, int index, Stack<Integer> p) {
if (p.size() == k) {
result.add(new ArrayList<>(p));
return;
}
for (int i = index; i <= n - (k - p.size()) + 1; i++) {
p.push(i);
findCombinations(n, k, i + 1, p);
p.pop();
}
}
public static void main(String[] args) {
List<List<Integer>> lists = new Solution3().combine(4, 2);
System.out.println(lists);
}
}