Combinations
Combinations is a medium recursion & backtracking problem solved with the pick / not-pick pattern.
The best approach, optimal (loop with pruning), runs in O(k · C(n, k)) time and O(k) space.
Below are 2 approaches in Java, from pick / not-pick over 1..n up.
Problem
Given n and k, return all combinations of k numbers chosen from 1..n, in any order. [1,2] and [2,1] are the same combination.
Examples
Example 1
- Input
n = 4, k = 2- Output
[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
Example 2
- Input
n = 3, k = 3- Output
[[1,2,3]]
Constraints
1 <= k <= n <= 20
Animated walkthrough
A narrated, step-by-step animation that builds the solution from the idea up. Press play, or step through it at your own pace.
Concepts first, then the problem and every approach, step by step.
Space to play or pause · ← → to jump a step · click or drag the bar to seek
Solutions
Try it yourself first. Then compare: each approach lists its idea, the steps, its time and space, and the Java code.
| Approach | Time | Space |
|---|---|---|
| Pick / not-pick over 1..n | O(k · C(n, k)) | O(n) |
| Optimal (loop with pruning) | O(k · C(n, k)) | O(k) |
1Pick / not-pick over 1..n
O(k · C(n, k))O(n)Recursion depth up to n.For each number decide to take it or skip it, and record when the path has k numbers.
- If path.size() == k, record.
- If i > n, return.
- Take i and recurse; skip i and recurse.
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> out = new ArrayList<>();
pick(1, n, k, new ArrayList<>(), out);
return out;
}
private void pick(int i, int n, int k, List<Integer> path, List<List<Integer>> out) {
if (path.size() == k) { out.add(new ArrayList<>(path)); return; }
if (i > n) return;
path.add(i);
pick(i + 1, n, k, path, out);
path.remove(path.size() - 1);
pick(i + 1, n, k, path, out);
}
}2Optimal (loop with pruning)
O(k · C(n, k))O(k)At each level loop over the next number from start. If fewer than k - path.size() numbers remain, no complete combination is possible, so stop the loop early.
- need = k - path.size(); if need == 0, record.
- for i from start to n - need + 1: add i, recurse with i + 1, remove.
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> out = new ArrayList<>();
build(1, n, k, new ArrayList<>(), out);
return out;
}
private void build(int start, int n, int k, List<Integer> path, List<List<Integer>> out) {
int need = k - path.size();
if (need == 0) { out.add(new ArrayList<>(path)); return; }
for (int i = start; i <= n - need + 1; i++) {
path.add(i);
build(i + 1, n, k, path, out);
path.remove(path.size() - 1);
}
}
}Edge cases to test
- k = n (one combination)
- k = 1
Hints
Hint 1
Only choose numbers larger than the last one so each set appears once, and stop early when too few numbers are left to reach k.
FAQ
What is the best time complexity for Combinations?
Optimal (loop with pruning) runs in O(k · C(n, k)) time and O(k) extra space.
Which pattern does Combinations use?
It is a recursion & backtracking problem that uses the pick / not-pick pattern. Other problems with the same pattern: Generate all binary strings without consecutive 1's, Subsets (2 choices per element), Subsets II.
Is there a brute force solution for Combinations?
Yes. Pick / not-pick over 1..n takes O(k · C(n, k)) time and O(n) space. For each number decide to take it or skip it, and record when the path has k numbers.
Which edge cases should I test for Combinations?
k = n (one combination); k = 1.