AlgoMaster Logo

Combinations

mediumFrequencyUpdated September 21, 2026

Understanding the Problem

We need to generate all possible ways to pick k numbers from the integers 1 through n, where order does not matter. This is the classic "n choose k" problem from combinatorics.

Unlike permutations, order does not matter. Picking [1, 3] is the same as picking [3, 1], so only one of them should appear in the output. We need a systematic way to explore choices without producing the same combination twice in different orders.

Picking numbers in increasing order solves this. If we have already picked the number 3, we only consider numbers greater than 3 for the next pick. Every combination is then generated exactly once, in sorted form.

Key Constraints:

  • 1 <= n <= 20 → The largest possible output is C(20, 10) = 184,756 combinations, and there are only 2^20 (about a million) subsets in total. Algorithms that enumerate every subset are acceptable here.
  • 1 <= k <= n → k never exceeds n, so there is always at least one valid combination.

Approach 1: Brute Force (Recursion Without Pruning)

Intuition

For a fixed k, nested loops generate every combination. With k = 2, an outer loop picks the first number and an inner loop picks the second, starting one past the first so the picks stay in increasing order. With k = 3 you would add a third loop, and so on. The pattern breaks down because k is an input: it can be anything from 1 to 20, and you cannot write a variable number of loops.

Recursion fixes this. Each recursive call plays the role of one loop level. The call receives a start value, loops i from start to n, appends i to the combination under construction, and recurses with start = i + 1 to fill the next slot. When the combination reaches length k, it is copied into the result. This enumerates the same sequences as the nested loops, for any k.

The cost of this direct translation is wasted work. The recursion enters branches that cannot produce a complete combination. With n = 4 and k = 2, picking 4 first leaves no numbers for the second slot, yet the call is still made and returns without recording anything. Approach 2 removes these dead ends.

Algorithm

  1. Define a recursive function backtrack(current, start). If current has k numbers, copy it into the result and return.
  2. Otherwise, loop i from start to n: append i to current, recurse with start = i + 1, then remove i to restore the state.
  3. Call backtrack with an empty list and start = 1, then return the result.
  4. Because each call only considers numbers greater than the last pick, the combinations come out in lexicographic order with no duplicates.

Example Walkthrough

Input:

4
n
2
k

The recursion with n = 4 and k = 2 runs as follows:

  • backtrack([], 1): pick 1 → backtrack([1], 2): pick 2 → [1,2] has length k, record it. Backtrack, pick 3 → record [1,3]. Backtrack, pick 4 → record [1,4].
  • Back at the top level, pick 2 → backtrack([2], 3): pick 3 → record [2,3], then pick 4 → record [2,4].
  • Pick 3 → backtrack([3], 4): pick 4 → record [3,4].
  • Pick 4 → backtrack([4], 5): the loop runs from 5 to 4, so it executes zero times. The branch ends without recording anything. This is the dead-end call that pruning will eliminate.

All branches are exhausted and the function returns six combinations:

0
1
0
1
2
1
1
3
2
1
4
3
2
3
4
2
4
5
3
4
result

Code

The wasted calls are predictable in advance: a branch cannot complete once fewer numbers remain than slots to fill. The next approach checks this condition before recursing.

Approach 2: Backtracking with Pruning

Intuition

The structure is the same as Approach 1: pick a number, recurse to fill the remaining slots, then remove it and try the next option, always picking in increasing order so no combination appears twice.

The improvement is the loop bound. Instead of letting i run all the way to n, stop it at n - remaining + 1, where remaining is the number of slots still to fill. Any value past that bound starts a branch that cannot finish.

Algorithm

  1. Start with an empty combination and begin from number 1.
  2. At each recursive call, if the current combination has k numbers, add it to the result and return.
  3. For each number i from start to n - remaining + 1 (pruned upper bound):
    • Add i to the current combination.
    • Recurse with start = i + 1.
    • Remove i from the current combination (backtrack).
  4. Return the collected result.

Example Walkthrough

1Start: current=[], need 2 numbers; pruned bound lets i run 1..3 at the root
1/11

Code

Recursion is not the only way to enumerate combinations. Each subset of {1, ..., n} can be encoded as an n-bit integer, which turns the whole problem into a single loop over bitmasks.

Approach 3: Iterative using Bitmasks

Intuition

Each combination of k numbers from {1, 2, ..., n} can be represented as a bitmask of length n, where the i-th bit is 1 if number (i+1) is included. For example, with n = 4 and k = 2, bitmask 0011 represents {1, 2} and 0101 represents {1, 3}.

Subsets and masks correspond one to one, so the problem reduces to enumerating every n-bit integer with exactly k set bits: iterate from 0 to 2^n - 1, keep the masks whose set-bit count equals k, and decode each one into a combination. Every combination appears exactly once, with no recursion and no duplicate handling. Because masks are visited in numeric order, the output order differs from the backtracking approaches ([2,3] is produced before [1,4]); the problem accepts any order.

The loop scans all 2^n masks regardless of k, so this does more work than pruned backtracking whenever C(n, k) is small. With n capped at 20, that is about a million masks, well within the limits here.

Algorithm

  1. Iterate through all integers from 0 to 2^n - 1.
  2. For each integer, count the number of set bits.
  3. If exactly k bits are set, convert the bitmask to a combination: for each set bit at position i, include number (i + 1).
  4. Add the combination to the result.

Example Walkthrough

1mask=0,1,2: fewer than 2 bits set, skip
0
0
1
0
2
0
3
0
1/8

Code