给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combinations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
回溯法 是一种通过遍历所有可能成员来寻找全部可行解的算法。若候选 不是 可行解 (或者至少不是 最后一个 解),回溯法会在前一步进行一些修改以舍弃该候选,换而言之, 回溯 并再次尝试。
这是一个回溯法函数,它将第一个添加到组合中的数和现有的组合作为参数。 backtrack(first, curr)
若组合完成- 添加到输出中。
遍历从 first t到 n的所有整数。
将整数 i 添加到现有组合 curr中。
继续向组合中添加更多整数 : backtrack(i + 1, curr).
将 i 从 curr中移除,实现回溯。
class Solution {
List<List<Integer>> output = new LinkedList();
int n;
int k;
public void backtrack(int first, LinkedList<Integer> curr) {
// if the combination is done
if (curr.size() == k)
output.add(new LinkedList(curr));
for (int i = first; i < n + 1; ++i) {
// add i into the current combination
curr.add(i);
// use next integers to complete the combination
backtrack(i + 1, curr);
// backtrack
curr.removeLast();
}
}
public List<List<Integer>> combine(int n, int k) {
this.n = n;
this.k = k;
backtrack(1, new LinkedList<Integer>());
return output;
}
}
时间复杂度 : O ( k C N k ) O(k C_N^k) O(kCNk),其中 C N k = N ! ( N − k ) ! k ! C_N^k = \frac{N!}{(N - k)! k!} CNk=(N−k)!k!N! 是要构成的组合数。 append / pop (add / removeLast) 操作使用常数时间,唯一耗费时间的是将长度为 k 的组合添加到输出中。
空间复杂度 : O ( C N k ) O(C_N^k) O(CNk),用于保存全部组合数以输出。
主要思路是以字典序的顺序获得全部组合。
算法非常直截了当 :
将 nums 初始化为从 1 到 k的整数序列。 将 n + 1添加为末尾元素,起到“哨兵”的作用。 将指针设为列表的开头 j = 0.
While j < k :
将nums 中的前k个元素添加到输出中,换而言之,除了“哨兵”之外的全部元素。
找到nums中的第一个满足 nums[j] + 1 != nums[j + 1]的元素,并将其加一 nums[j]++ 以转到下一个组合。
class Solution {
public List<List<Integer>> combine(int n, int k) {
// init first combination
LinkedList<Integer> nums = new LinkedList<Integer>();
for(int i = 1; i < k + 1; ++i)
nums.add(i);
nums.add(n + 1);
List<List<Integer>> output = new ArrayList<List<Integer>>();
int j = 0;
while (j < k) {
// add current combination
output.add(new LinkedList(nums.subList(0, k)));
// increase first nums[j] by one
// if nums[j] + 1 != nums[j + 1]
j = 0;
while ((j < k) && (nums.get(j + 1) == nums.get(j) + 1))
nums.set(j, j++ + 1);
nums.set(j, nums.get(j) + 1);
}
return output;
}
}
时间复杂度 : O ( k C N k ) O(k C_N^k) O(kCNk),其中 C N k = N ! ( N − k ) ! k ! C_N^k = \frac{N!}{(N - k)! k!} CNk=(N−k)!k!N! 是要构建的组合数。由于组合数是 C N k C_N^k CNk,外层的 while 循环执行了 C N k C_N^k CNk 次 。对给定的一个j,内层的 while 循环执行了 C N − j k − j C_{N - j}^{k - j} CN−jk−j 次。外层循环超过 C N k C_N^k CNk 次访问,平均而言每次访问的执行次数少于1。因此,最耗费时间的部分是将每个长度为k 的组合(共计 C N k C_N^k CNk个组合) 添加到输出中, 消耗 O ( k C N k ) O(k C_N^k) O(kCNk) 的时间。
你可能注意到,尽管方法二的时间复杂度与方法一相同,但方法二却要快上许多。这是由于方法一需要处理递归调用栈,且其带来的影响在Python中比在Java中更为显著。
空间复杂度 : O ( C N k ) O(C_N^k) O(CNk) ,用于保存全部组合数以输出。
作者:LeetCode
链接:https://leetcode-cn.com/problems/two-sum/solution/zu-he-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
执行用时 :68 ms, 在所有 Java 提交中击败了39.81%的用户
内存消耗 :41.9 MB, 在所有 Java 提交中击败了85.64%的用户
class Solution {
List<List<Integer>> res;
int k = 0;
int n = 0;
public List<List<Integer>> combine(int n, int k) {
res = new LinkedList<>();
if(k<=0) return res;
this.k = k;
this.n = n;
LinkedList<Integer> list = new LinkedList<>();
backtrack(0,list);
return res;
}
private void backtrack(int i,LinkedList<Integer> list){
if(list.size()==k) res.add(new LinkedList<>(list));
else{
for(int j=i;j<n;j++){
list.add(j+1);
backtrack(j+1,list);
list.removeLast();
}
}
}
}