Facebook Pixel

3821. Find Nth Smallest Integer With K One Bits

HardBit ManipulationMathCombinatorics
LeetCode ↗

Problem Description

You are given two positive integers n and k.

Your task is to find the nth smallest positive integer whose binary representation contains exactly k ones (set bits).

In other words, among all positive integers that have precisely k ones in their binary form, list them in increasing order, and return the one that appears in the nth position.

It is guaranteed that the answer is strictly less than 2^50.

Example walkthrough:

Consider k = 2. The positive integers with exactly two ones in their binary representation, listed in increasing order, are:

  • 3 → binary 11
  • 5 → binary 101
  • 6 → binary 110
  • 9 → binary 1001
  • 10 → binary 1010
  • 12 → binary 1100
  • ...

If n = 4 and k = 2, then the 4th number in this sequence is 9.

Key points to understand:

  • A number's binary representation must have exactly k set bits — not more, not fewer.
  • The numbers are considered in ascending order, and you must return the value at the nth position.
  • Since the answer is guaranteed to be less than 2^50, you only need to consider integers that fit within 50 bits.
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Math / Bit Manipulation?

This problem maps to Math / Bit Manipulation through a short path in the full flowchart.

Math orbittricks?yesPrime orGCD?noMath / BitManipulation

Using combinatorics (nCr) to count numbers with exactly k set bits and locate the nth one involves combinatorial math and bit manipulation.

Open in Flowchart

Intuition

A natural first idea is to simply count upward, checking each integer's number of set bits until we reach the nth one. But since the answer can be as large as 2^50, this brute-force approach is far too slow.

The key insight is to build the answer bit by bit, from the most significant bit down to the least significant bit, instead of searching number by number.

The crucial observation is this: when deciding the value of a particular bit, we can use combinatorics to count how many valid numbers fall on each side of that decision — without actually listing them.

Let's think about it carefully. Suppose we are looking at bit position i (where positions range from 49 down to 0), and we still need to place k ones among bits 0 through i.

  • If we decide to set bit i to 0, then all k ones must be distributed among the lower i bits (positions 0 to i-1). The number of distinct ways to choose where those k ones go is exactly C(i, k) — that is, "choose k positions out of i". Every such arrangement produces a number smaller than any number where bit i is 1.

  • If we decide to set bit i to 1, then we have used one of our ones, and we still need to place k-1 ones in the remaining lower bits.

So the numbers with bit i equal to 0 are precisely the C(i, k) smallest options at this step, and the numbers with bit i equal to 1 come after them.

This gives us a clean decision rule:

  • If n > C(i, k), the nth number cannot be among the first C(i, k) smaller options, so bit i must be 1. We then "skip past" those C(i, k) smaller numbers by subtracting them: n -= C(i, k), and decrease k by 1 since we just used one of our ones.

  • Otherwise (n <= C(i, k)), the nth number lies within the group where bit i is 0, so we leave bit i as 0 and move on to the next lower bit without changing n or k.

This is essentially a greedy ranking process, similar to how we find the nth permutation by deciding each digit one at a time. Each step locks in one bit and narrows down which "slice" of the ordering our target falls into.

We repeat this from bit 49 down to bit 0. The moment k reaches 0, all required ones have been placed, and the remaining lower bits stay 0, so we can stop early.

To make the combination counts C(i, k) instant to look up, we precompute Pascal's triangle once before answering. This lets the entire bit-by-bit construction run in just 50 steps, making the solution extremely fast regardless of how large n is.

Pattern Learn more about Math and Combinatorics patterns.

Solution Approach

Combinatorics + Greedy

We construct the answer one bit at a time, from the most significant bit (position 49) down to the least significant bit (position 0), using precomputed binomial coefficients to make each decision instantly.

Step 1: Precompute Pascal's Triangle

Before solving any query, we build a table c where c[i][j] stores the value of C(i, j) — the number of ways to choose j items out of i. This is done using the classic recurrence:

c[i][j] = c[i-1][j-1] + c[i-1][j]

with the base case c[i][0] = 1 for every i. We fill this table for all i up to mx = 50, which covers every combination count we might need. Computing it once upfront means each lookup of C(i, k) during the main loop costs O(1).

Step 2: Greedily decide each bit

We initialize ans = 0 and loop over bit positions i from 49 down to 0. At each position, we ask: if bit i were 0, how many valid numbers would be smaller? That count is exactly c[i][k] — the number of ways to place all k remaining ones among the lower i bits.

  • If n > c[i][k]: the nth number cannot fall into the group where bit i is 0, so bit i must be 1. We perform three updates:

    • n -= c[i][k] — skip past the c[i][k] smaller numbers we have ruled out.
    • ans |= 1 << i — set bit i of the answer to 1.
    • k -= 1 — we have used one of our k ones.

    After decrementing k, if k == 0, all ones have been placed, so we break out of the loop early. The remaining lower bits stay 0, which is already their default value in ans.

  • Otherwise (n <= c[i][k]): the nth number lies within the group where bit i is 0. We leave bit i as 0, keep n and k unchanged, and continue to the next lower bit.

Step 3: Return the result

Once the loop finishes (either by reaching bit 0 or by k dropping to 0), ans holds the fully constructed number, which we return.

Complexity Analysis

  • Time Complexity: The precomputation of Pascal's triangle takes O(mx^2) once. Each call to nthSmallest runs a single loop over at most 50 bit positions, each doing O(1) work, so each query is O(50), effectively constant.

  • Space Complexity: The table c uses O(mx^2) space, which is also a one-time cost. The query itself uses only O(1) extra space.

Data Structures and Patterns Used

  • Pascal's Triangle (2D DP table): a precomputed lookup table for binomial coefficients C(i, j), enabling instant combination counts.
  • Greedy bit construction: deciding each bit from high to low, similar to finding the nth permutation digit by digit.
  • Combinatorial ranking: using C(i, k) to count how many candidates lie "below" a given choice, allowing us to jump directly to the correct number rather than enumerating.

Example Walkthrough

Let's trace through the solution with a small example: n = 4, k = 2. We expect the answer to be 9 (binary 1001), as shown in the problem description.

To keep the walkthrough readable, we'll loop over bit positions from a small upper bound rather than 49 — the first set bit of our answer is at position 3, so positions above that contribute nothing. We start at bit position i = 4 and work downward.

Precomputed combination values we'll need:

C(i, k)Value
C(4, 2)6
C(3, 2)3
C(2, 2)1
C(2, 1)2
C(1, 1)1

We initialize ans = 0, n = 4, k = 2.

Bit i = 4: How many valid numbers exist if bit 4 is 0? That's C(4, 2) = 6.

  • Compare: n = 4 > 6? No. The 4th number lies among the group where bit 4 is 0.
  • Leave bit 4 as 0. State unchanged: n = 4, k = 2, ans = 0.

Bit i = 3: If bit 3 is 0, count is C(3, 2) = 3.

  • Compare: n = 4 > 3? Yes. The 4th number cannot be in the first 3 smaller numbers, so bit 3 must be 1.
  • n -= 3n = 1
  • ans |= 1 << 3ans = 8 (binary 1000)
  • k -= 1k = 1
  • k == 0? No, continue.

Bit i = 2: If bit 2 is 0, count is C(2, 1) = 2.

  • Compare: n = 1 > 2? No. The target lies in the group where bit 2 is 0.
  • Leave bit 2 as 0. State unchanged: n = 1, k = 1, ans = 8.

Bit i = 1: If bit 1 is 0, count is C(1, 1) = 1.

  • Compare: n = 1 > 1? No. The target lies in the group where bit 1 is 0.
  • Leave bit 1 as 0. State unchanged: n = 1, k = 1, ans = 8.

Bit i = 0: If bit 0 is 0, count is C(0, 1) = 0.

  • Compare: n = 1 > 0? Yes. Bit 0 must be 1.
  • n -= 0n = 1
  • ans |= 1 << 0ans = 9 (binary 1001)
  • k -= 1k = 0
  • k == 0? Yes — break.

Result: ans = 9 (binary 1001), which has exactly k = 2 set bits. ✅

This matches the expected 4th number in the sequence 3, 5, 6, 9, .... Notice how the algorithm never enumerated any candidate numbers — at each bit it used a single combination lookup to decide whether to "jump past" a whole slice of smaller numbers (set the bit to 1) or descend into that slice (leave the bit 0).

Solution Implementation

1# Precompute binomial coefficients (Pascal's triangle).
2# MAX_BITS is the upper bound on the number of bits we may need.
3MAX_BITS = 50
4
5# binomial[i][j] = C(i, j) = number of ways to choose j items from i items
6binomial = [[0] * (MAX_BITS + 1) for _ in range(MAX_BITS)]
7for i in range(MAX_BITS):
8    binomial[i][0] = 1  # C(i, 0) is always 1
9    for j in range(1, i + 1):
10        # Pascal's rule: C(i, j) = C(i-1, j-1) + C(i-1, j)
11        binomial[i][j] = binomial[i - 1][j - 1] + binomial[i - 1][j]
12
13
14class Solution:
15    def nthSmallest(self, n: int, k: int) -> int:
16        """
17        Return the n-th smallest non-negative integer whose binary
18        representation contains exactly k set bits (1s).
19
20        Uses the combinatorial number system: scan bit positions from
21        high to low and greedily decide whether each bit should be set.
22        """
23        ans = 0
24
25        # Iterate bit positions from the most significant down to the least.
26        for bit in range(MAX_BITS - 1, -1, -1):
27            # binomial[bit][k] = how many numbers can be formed using the
28            # lower `bit` positions while still needing k set bits, i.e.
29            # the count of candidates that keep this bit unset.
30            if n > binomial[bit][k]:
31                # Not enough candidates with this bit unset, so it must
32                # be set. Skip past those candidates.
33                n -= binomial[bit][k]
34                ans |= 1 << bit  # set this bit
35                k -= 1           # one fewer set bit remains to place
36
37                # All required set bits placed; remaining bits stay 0.
38                if k == 0:
39                    break
40
41        return ans
42
1class Solution {
2    // Upper bound for the number of bits we consider (covers values up to 2^49).
3    private static final int MAX_BITS = 50;
4
5    // Precomputed Pascal's triangle: binomial[i][j] = C(i, j),
6    // i.e. the number of ways to choose j items out of i.
7    private static final long[][] binomial = new long[MAX_BITS][MAX_BITS + 1];
8
9    // Static initializer: build the binomial coefficient table once at class load time.
10    static {
11        for (int i = 0; i < MAX_BITS; i++) {
12            // There is exactly one way to choose 0 items.
13            binomial[i][0] = 1;
14            // Standard Pascal's triangle recurrence: C(i, j) = C(i-1, j-1) + C(i-1, j).
15            for (int j = 1; j <= i; j++) {
16                binomial[i][j] = binomial[i - 1][j - 1] + binomial[i - 1][j];
17            }
18        }
19    }
20
21    /**
22     * Returns the n-th smallest non-negative integer whose binary representation
23     * contains exactly k set bits (1-indexed by n).
24     *
25     * Strategy: decide each bit from the highest position down to the lowest.
26     * For bit position i, the number of valid integers that have a 0 at bit i
27     * (and the remaining k set bits placed among the lower i positions) is C(i, k).
28     * If n is within that range, bit i stays 0; otherwise we must set bit i,
29     * subtract the skipped count, and reduce the remaining set-bit budget k.
30     *
31     * @param n the 1-indexed rank of the desired value
32     * @param k the exact number of set bits required
33     * @return the n-th smallest integer with exactly k set bits
34     */
35    public long nthSmallest(long n, int k) {
36        long ans = 0;
37
38        // Iterate from the highest considered bit down to bit 0.
39        for (int i = 49; i >= 0; i--) {
40            // C(i, k) counts how many valid numbers keep bit i as 0.
41            if (n > binomial[i][k]) {
42                // Skip all those candidates that have bit i = 0.
43                n -= binomial[i][k];
44                // Set bit i in the answer.
45                ans |= 1L << i;
46                // One fewer set bit remains to be placed.
47                k--;
48                // Once all required set bits are placed, the result is complete.
49                if (k == 0) {
50                    break;
51                }
52            }
53        }
54
55        return ans;
56    }
57}
58
1// Maximum number of bits we consider (covers values up to ~2^49).
2constexpr int kMaxBits = 50;
3
4// comb[i][j] = C(i, j), the number of ways to choose j items from i.
5long long comb[kMaxBits][kMaxBits + 1];
6
7// Precompute Pascal's triangle once at program startup.
8// The lambda is invoked immediately, and its return value initializes a dummy variable.
9const auto kInit = [] {
10    for (int i = 0; i < kMaxBits; ++i) {
11        comb[i][0] = 1;  // C(i, 0) = 1
12        for (int j = 1; j <= i; ++j) {
13            // Pascal's identity: C(i, j) = C(i-1, j-1) + C(i-1, j).
14            comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
15        }
16    }
17    return 0;
18}();
19
20class Solution {
21public:
22    // Returns the n-th smallest positive integer (1-indexed) whose binary
23    // representation contains exactly k set bits.
24    long long nthSmallest(long long n, int k) {
25        long long answer = 0;
26
27        // Decide each bit from the most significant (49) down to the least (0).
28        for (int i = 49; i >= 0; --i) {
29            // comb[i][k] counts how many numbers can be formed using the lower
30            // i bit positions with k bits still to place (i.e. leaving bit i unset).
31            // If n is larger, those candidates are skipped, so bit i must be set.
32            if (n > comb[i][k]) {
33                n -= comb[i][k];     // Skip the candidates that leave bit i unset.
34                answer |= 1LL << i;  // Set bit i in the answer.
35
36                // One set bit has been placed; if no more are needed, we are done.
37                if (--k == 0) {
38                    break;
39                }
40            }
41        }
42
43        return answer;
44    }
45};
46
1// Maximum number of bits / table dimension
2const MAX_BITS = 50;
3
4// binomial[i][j] stores C(i, j) = "i choose j", using BigInt for large values
5const binomial: bigint[][] = Array.from({ length: MAX_BITS }, () =>
6    Array(MAX_BITS + 1).fill(0n)
7);
8
9// Precompute Pascal's triangle:
10// C(i, 0) = 1, and C(i, j) = C(i-1, j-1) + C(i-1, j)
11for (let i = 0; i < MAX_BITS; i++) {
12    binomial[i][0] = 1n;
13    for (let j = 1; j <= i; j++) {
14        binomial[i][j] = binomial[i - 1][j - 1] + binomial[i - 1][j];
15    }
16}
17
18/**
19 * Find the n-th smallest non-negative integer whose binary representation
20 * contains exactly k set bits (ones).
21 *
22 * Strategy: Decide each bit from the highest position down to the lowest.
23 * For a given bit position i, the number of valid integers that keep this
24 * bit as 0 (i.e. all remaining k ones are placed among the lower i bits)
25 * is C(i, k). If n falls within that count, leave the bit unset; otherwise
26 * set this bit, subtract that count, and reduce the number of ones still
27 * needed (k) by one.
28 *
29 * @param n The 1-based rank of the desired number.
30 * @param k The required number of set bits.
31 * @returns The n-th smallest integer with exactly k set bits.
32 */
33function nthSmallest(n: number, k: number): number {
34    // remaining rank we still need to skip past
35    let remainingRank = BigInt(n);
36    // the result accumulated bit by bit
37    let result = 0n;
38
39    // Iterate bit positions from the highest down to the lowest
40    for (let bit = 49; bit >= 0; bit--) {
41        // Count of numbers with this bit unset and k ones in the lower bits
42        const count = binomial[bit][k];
43
44        // If the rank exceeds this count, this bit must be set
45        if (remainingRank > count) {
46            remainingRank -= count;
47            result |= 1n << BigInt(bit);
48
49            // One fewer set bit is needed; stop once all ones are placed
50            if (--k === 0) {
51                break;
52            }
53        }
54    }
55
56    return Number(result);
57}
58

Time and Space Complexity

The time complexity is O(log² M), and the space complexity is O(log² M), where M is the upper bound of the answer, 2^50.

Time Complexity Analysis

The precomputation of the binomial coefficient table involves a nested loop. The outer loop runs mx times, and the inner loop runs up to i + 1 times, resulting in O(mx²) operations. Since mx corresponds to log M (the number of bits, here 50), this precomputation costs O(log² M).

The nthSmallest method iterates from bit position 49 down to 0, performing constant-time work in each iteration. This loop runs at most O(log M) times, contributing O(log M) to the overall time.

Combining both parts, the dominant term is the table precomputation, so the total time complexity is O(log² M).

Space Complexity Analysis

The two-dimensional array c has dimensions mx × (mx + 1), storing O(mx²) entries. Since mx corresponds to log M, the space required is O(log² M). The remaining variables in nthSmallest use only constant extra space, so the overall space complexity is O(log² M).

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Off-by-One Confusion Between 0-Indexed and 1-Indexed Counting

The most frequent mistake is mishandling the comparison between n and binomial[bit][k]. The natural instinct is to write the condition as n >= binomial[bit][k], but this is wrong and produces an off-by-one error.

Why it's wrong: binomial[bit][k] counts how many valid numbers exist when the current bit is left unset (all k ones must fit in the lower bit positions). These candidates occupy ranks 1 through binomial[bit][k]. The nth number belongs to this "bit unset" group precisely when n <= binomial[bit][k]. Therefore, we set the bit only when n > binomial[bit][k].

# WRONG: incorrectly classifies the boundary case where n exactly
# equals binomial[bit][k], pushing it into the "set bit" branch.
if n >= binomial[bit][k]:
    n -= binomial[bit][k]
    ...

# CORRECT: the equality case belongs to the "bit unset" group.
if n > binomial[bit][k]:
    n -= binomial[bit][k]
    ...

Concrete example: With k = 2, n = 1, the answer should be 3 (binary 11). If you used >=, at bit = 1, binomial[1][2] = 0, so n >= 0 is true and you would wrongly set bit 1 while still having n = 1, corrupting the construction. The strict > keeps the logic aligned with 1-indexed ranking.

Pitfall 2: Insufficient Pascal's Triangle Dimensions

The table is declared as binomial[i][j] for i in range(MAX_BITS) and j up to MAX_BITS. A subtle trap is accessing binomial[bit][k] when k could exceed the inner dimension, or when bit < k.

The safe-by-design detail: When bit < k, you cannot fit k ones into bit positions, so C(bit, k) must be 0. The initialization [[0] * (MAX_BITS + 1)] ensures these entries are 0 by default, and the inner loop for j in range(1, i + 1) never overwrites them. If you mistakenly looped j only up to some smaller bound, or shrank the column dimension below the maximum possible k, you would read garbage or trigger an IndexError.

# WRONG: inner dimension too small — if k can be up to 50,
# accessing binomial[bit][50] raises IndexError.
binomial = [[0] * MAX_BITS for _ in range(MAX_BITS)]  # only 0..49 columns

# CORRECT: allocate MAX_BITS + 1 columns to safely index k.
binomial = [[0] * (MAX_BITS + 1) for _ in range(MAX_BITS)]

Pitfall 3: Forgetting the Early break and Leaving k Unhandled

If you omit the if k == 0: break, the loop continues to lower bits. Although binomial[bit][0] = 1 for every bit, leaving k = 0 means every subsequent comparison becomes n > 1. If n was reduced to exactly 1 (its expected terminal value once all ones are placed), the condition n > 1 is false, so no extra bits get set and the answer stays correct — but relying on this is fragile.

Why the break matters: It makes the termination explicit and guards against any scenario where n hasn't settled to 1. Without it, a logic error elsewhere could silently set spurious low bits. The defensive break documents intent and short-circuits the remaining work for an O(remaining bits) saving.

# Risky: relies on n already being 1 and binomial[bit][0] == 1
# to avoid setting extra bits. Harder to reason about.
if n > binomial[bit][k]:
    n -= binomial[bit][k]
    ans |= 1 << bit
    k -= 1
    # no break

# Robust: stop immediately once all k ones are placed.
if n > binomial[bit][k]:
    n -= binomial[bit][k]
    ans |= 1 << bit
    k -= 1
    if k == 0:
        break

Pitfall 4: Misinterpreting the Bit Range (Including or Excluding Bit 49)

The loop runs for bit in range(MAX_BITS - 1, -1, -1), i.e. from bit 49 down to bit 0. A common error is writing range(MAX_BITS, -1, -1) (starting at bit 50) or range(MAX_BITS - 1, 0, -1) (stopping at bit 1 and skipping bit 0).

  • Starting at bit 50 would attempt 1 << 50, exceeding the guaranteed 2^50 bound and reading binomial[50][k], which is out of range.
  • Stopping at bit 1 skips the least significant bit entirely, so any answer needing bit 0 set (such as odd numbers) would be wrong.
# WRONG: skips bit 0, breaks all numbers requiring the lowest bit.
for bit in range(MAX_BITS - 1, 0, -1):
    ...

# CORRECT: includes bit 0 as the final position considered.
for bit in range(MAX_BITS - 1, -1, -1):
    ...

Always verify the loop covers exactly bits 0 through 49, matching the problem's 2^50 upper bound.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More