🧠
Brute Force (Pure Recursion)
💡 Starting with brute force helps understand the problem's recursive nature and the combinatorial explosion, even though it's not efficient for large inputs.
Intuition
Try every possible combination by recursively choosing or skipping each number, building combinations until the size k is reached.
Algorithm
- Start from number 1 and try to include it in the current combination.
- Recursively proceed to the next number, adding it if the combination size is less than k.
- If the combination size reaches k, add it to the results.
- Backtrack by removing the last added number and try the next possibility.
💡 The challenge is to manage the recursion and backtracking correctly so that all unique combinations are generated without duplicates.
from typing import List
def combine(n: int, k: int) -> List[List[int]]:
result = []
def backtrack(start: int, path: List[int]):
if len(path) == k:
result.append(path[:])
return
for i in range(start, n + 1):
path.append(i)
backtrack(i + 1, path)
path.pop()
backtrack(1, [])
return result
# Example usage
if __name__ == '__main__':
print(combine(4, 2))
Line Notes
def combine(n: int, k: int) -> List[List[int]]:Defines the function signature with input and output types for clarity.
def backtrack(start: int, path: List[int]):Helper function to build combinations starting from 'start' index.
if len(path) == k:Base case: when current combination size equals k, add a copy to results.
for i in range(start, n + 1):Iterate over remaining numbers to include in the combination.
path.append(i)Choose the current number and add it to the current path.
backtrack(i + 1, path)Recurse with next starting number to avoid duplicates and maintain order.
path.pop()Backtrack by removing the last number to explore other possibilities.
return resultReturn all collected combinations after recursion completes.
import java.util.*;
public class Combinations {
public static List<List<Integer>> combine(int n, int k) {
List<List<Integer>> result = new ArrayList<>();
backtrack(1, n, k, new ArrayList<>(), result);
return result;
}
private static void backtrack(int start, int n, int k, List<Integer> path, List<List<Integer>> result) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i <= n; i++) {
path.add(i);
backtrack(i + 1, n, k, path, result);
path.remove(path.size() - 1);
}
}
public static void main(String[] args) {
System.out.println(combine(4, 2));
}
}
Line Notes
public static List<List<Integer>> combine(int n, int k) {Main function to initiate combination generation.
backtrack(1, n, k, new ArrayList<>(), result);Start recursion from number 1 with empty path.
if (path.size() == k) {Base case: when combination size reaches k, add a copy to results.
for (int i = start; i <= n; i++) {Loop through numbers from start to n to build combinations.
path.add(i);Choose current number to include in combination.
backtrack(i + 1, n, k, path, result);Recurse with next number to avoid duplicates.
path.remove(path.size() - 1);Backtrack by removing last number to try other options.
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
void backtrack(int start, int n, int k, vector<int>& path, vector<vector<int>>& result) {
if (path.size() == k) {
result.push_back(path);
return;
}
for (int i = start; i <= n; i++) {
path.push_back(i);
backtrack(i + 1, n, k, path, result);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> result;
vector<int> path;
backtrack(1, n, k, path, result);
return result;
}
};
int main() {
Solution sol;
vector<vector<int>> res = sol.combine(4, 2);
for (auto& comb : res) {
cout << "[";
for (int i = 0; i < comb.size(); i++) {
cout << comb[i];
if (i < comb.size() - 1) cout << ",";
}
cout << "] ";
}
cout << endl;
return 0;
}
Line Notes
void backtrack(int start, int n, int k, vector<int>& path, vector<vector<int>>& result) {Recursive helper to build combinations starting from 'start'.
if (path.size() == k) {Base case: when combination size equals k, add to results.
for (int i = start; i <= n; i++) {Iterate over remaining numbers to include in combination.
path.push_back(i);Include current number in the path.
backtrack(i + 1, n, k, path, result);Recurse with next number to avoid duplicates.
path.pop_back();Backtrack by removing last number to explore other options.
vector<vector<int>> combine(int n, int k) {Main function to start recursion and return results.
function combine(n, k) {
const result = [];
function backtrack(start, path) {
if (path.length === k) {
result.push([...path]);
return;
}
for (let i = start; i <= n; i++) {
path.push(i);
backtrack(i + 1, path);
path.pop();
}
}
backtrack(1, []);
return result;
}
// Example usage
console.log(combine(4, 2));
Line Notes
function combine(n, k) {Defines main function to generate combinations.
function backtrack(start, path) {Helper recursive function to build combinations.
if (path.length === k) {Base case: when combination size reaches k, add a copy to results.
for (let i = start; i <= n; i++) {Loop through numbers from start to n to build combinations.
path.push(i);Choose current number to include in combination.
backtrack(i + 1, path);Recurse with next number to avoid duplicates.
path.pop();Backtrack by removing last number to try other options.
TimeO(n choose k * k)
SpaceO(k) for recursion stack + O(n choose k * k) for output
We explore all combinations of size k from n elements, which is n choose k. Each combination takes O(k) to copy into results.
💡 For n=20 and k=10, this means exploring about 184,756 combinations, which is large but manageable for medium constraints.
Interview Verdict: Accepted
This approach works well for moderate n and k, but can be slow for very large inputs due to combinatorial explosion.