Facebook Pixel

3881. Direction Assignments with Exactly K Visible People

LeetCode ↗

Problem Description

You are given three integers n, pos, and k.

There are n people standing in a line, indexed from 0 to n - 1. Each person independently picks a direction to face:

  • 'L': this person is visible only to people standing to their right.
  • 'R': this person is visible only to people standing to their left.

We focus on the person located at index pos. This person sees others according to the following rules:

  • A person i < pos (standing to the left of pos) is visible if and only if they choose 'L'.
  • A person i > pos (standing to the right of pos) is visible if and only if they choose 'R'.

In other words, for someone to be visible to the person at pos, they must be facing toward pos.

Every one of the n people (including the person at pos) chooses a direction, so there are many possible ways to assign directions to the whole line. Your task is to count how many of these direction assignments make the person at index pos see exactly k people.

Since the result can be very large, return the count modulo 10^9 + 7.

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.

Bitmanipulationor math?yesNumbertheory?noMath / BitManipulation

The problem counts direction assignments using combinatorial enumeration and binomial coefficients, a pure counting/math approach.

Open in Flowchart

Intuition

The key observation is that the people on the left of pos and the people on the right of pos behave completely independently, and the choices made by these two groups don't interfere with each other.

Let's count how many people are on each side:

  • There are l = pos people to the left.
  • There are r = n - pos - 1 people to the right.

For the person at pos to see someone on the left, that left person must choose 'L'. For the person at pos to see someone on the right, that right person must choose 'R'. So "being visible" simply means a person is facing toward pos.

Now, suppose the person at pos sees exactly a people on the left and exactly b people on the right. Since the total must be k, we have a + b = k. This gives us a natural way to split the problem: enumerate how many visible people come from the left side, and the rest must come from the right side.

For a fixed a:

  • We need to pick which a of the l left people are facing 'L' (visible), while the remaining l - a face 'R'. The number of ways to choose them is C(l, a).
  • Similarly, we pick which b = k - a of the r right people are facing 'R' (visible), with the rest facing 'L'. The number of ways is C(r, b).

Because the left and right choices are independent, the number of valid assignments for this particular split is the product C(l, a) * C(r, b).

One detail remains: the person at pos also chooses a direction. Their own choice ('L' or 'R') has no effect on who they can see, so it is always free. This adds a factor of 2 to every valid configuration.

Putting it together, we sum over all valid values of a (where 0 <= a <= min(k, l) and b = k - a <= r):

ans = sum over valid a of 2 * C(l, a) * C(r, b)

To compute the binomial coefficients C(n, k) quickly under the modulo, we precompute factorials and their modular inverses, so each combination is just a constant-time multiplication.

Pattern Learn more about Math and Combinatorics patterns.

Solution Approach

We use Combinatorics + Enumeration, supported by precomputed factorials and modular inverses for fast binomial coefficient calculation.

Step 1: Precompute factorials and inverse factorials

Since we will repeatedly compute binomial coefficients C(n, k) under the modulus MOD = 10^9 + 7, we precompute two arrays up to a large limit N = 100001:

  • f[i] stores i! modulo MOD.
  • g[i] stores the modular inverse of i!, i.e. (i!)^{-1} modulo MOD.
f = [1] * N
g = [1] * N
for i in range(1, N):
    f[i] = f[i - 1] * i % MOD
    g[i] = pow(f[i], MOD - 2, MOD)

Here f[i] is built by multiplying the previous factorial by i. For the inverse, we rely on Fermat's Little Theorem: since MOD is prime, the inverse of any value x is pow(x, MOD - 2, MOD). So g[i] is the inverse of f[i].

Step 2: Define the combination function

With factorials and their inverses ready, each binomial coefficient is a constant-time computation using the formula C(n, k) = n! / (k! * (n - k)!):

def comb(n, k):
    return f[n] * g[k] * g[n - k] % MOD

Step 3: Set up the two groups

We split the line into the people on the left and the people on the right of pos:

  • l = pos is the number of people on the left.
  • r = n - pos - 1 is the number of people on the right.

Step 4: Enumerate the left split

We let a be the number of visible people on the left, so b = k - a must be the number of visible people on the right. We loop a from 0 to min(k, l), because we can't see more visible people than exist on the left, and we can't exceed k in total.

For each a, we check that b = k - a is valid, meaning b <= r (we can't have more visible right people than exist). When valid, we add the contribution:

2 * comb(l, a) * comb(r, b)

The factor 2 accounts for the person at pos freely choosing 'L' or 'R' without affecting visibility. We accumulate the result and keep it under the modulus.

class Solution:
    def countVisiblePeople(self, n: int, pos: int, k: int) -> int:
        l, r = pos, n - pos - 1
        ans = 0
        for a in range(min(k, l) + 1):
            b = k - a
            if b <= r:
                ans += 2 * comb(l, a) * comb(r, b)
                ans %= MOD
        return ans

Complexity Analysis

  • Time complexity: The precomputation is O(N) (done once, where N includes the cost of one modular exponentiation per index). Each query loops at most min(k, l) + 1 times with constant work per iteration, so each call runs in O(min(k, pos)) time.
  • Space complexity: O(N) for the two precomputed arrays f and g.

Example Walkthrough

Let's trace through a concrete example with n = 4, pos = 1, and k = 1.

Setup: Identify the two groups

The line has 4 people at indices 0, 1, 2, 3, and we focus on the person at pos = 1.

  • l = pos = 1 — there is 1 person on the left (index 0).
  • r = n - pos - 1 = 4 - 1 - 1 = 2 — there are 2 people on the right (indices 2 and 3).

We want the person at index 1 to see exactly k = 1 person.

Enumerate the left split

We let a = number of visible people on the left, and b = k - a = number of visible people on the right. We loop a from 0 to min(k, l) = min(1, 1) = 1.


Case a = 0 (see 0 people on the left, so b = 1 on the right):

Check validity: b = 1 <= r = 2. ✅ Valid.

  • comb(l, a) = C(1, 0) = 1 — the single left person must face 'R' (away from pos), only 1 way.
  • comb(r, b) = C(2, 1) = 2 — choose which of the 2 right people faces 'R' (toward pos); the other faces 'L'.

Contribution: 2 * C(1, 0) * C(2, 1) = 2 * 1 * 2 = 4.

The factor 2 accounts for the person at pos choosing either 'L' or 'R' freely.


Case a = 1 (see 1 person on the left, so b = 0 on the right):

Check validity: b = 0 <= r = 2. ✅ Valid.

  • comb(l, a) = C(1, 1) = 1 — the single left person must face 'L' (toward pos), only 1 way.
  • comb(r, b) = C(2, 0) = 1 — both right people must face 'L' (away from pos), only 1 way.

Contribution: 2 * C(1, 1) * C(2, 0) = 2 * 1 * 1 = 2.


Combine the results

ans = 4 + 2 = 6.

Verification by listing

Let's confirm by checking each visible configuration. Writing directions as [idx0, idx2, idx3] (the person at pos is omitted since their direction is free):

Left (idx 0)Right (idx 2, 3)Who is visible to posCount
RR, Lidx 21 ✅
RL, Ridx 31 ✅
LL, Lidx 01 ✅

There are 3 distinct visibility patterns that yield exactly k = 1. Each pattern is then multiplied by 2 for the free choice at pos, giving 3 * 2 = 6, which matches our computed answer.

The final result is 6.

Solution Implementation

1MAX_N = 100001
2MOD = 10**9 + 7
3
4# fact[i]  = i! mod MOD
5# inv_fact[i] = modular multiplicative inverse of i! mod MOD
6fact = [1] * MAX_N
7inv_fact = [1] * MAX_N
8for i in range(1, MAX_N):
9    fact[i] = fact[i - 1] * i % MOD
10    # Fermat's little theorem: a^(MOD-2) is the modular inverse of a (MOD prime)
11    inv_fact[i] = pow(fact[i], MOD - 2, MOD)
12
13
14def comb(n: int, k: int) -> int:
15    """Return C(n, k) modulo MOD using precomputed factorials."""
16    return fact[n] * inv_fact[k] * inv_fact[n - k] % MOD
17
18
19class Solution:
20    def countVisiblePeople(self, n: int, pos: int, k: int) -> int:
21        # left  = number of people standing to the left of pos
22        # right = number of people standing to the right of pos
23        left, right = pos, n - pos - 1
24
25        ans = 0
26        # Choose 'a' people from the left side; the rest 'b' come from the right.
27        # 'a' cannot exceed either k or the available people on the left.
28        for a in range(min(k, left) + 1):
29            b = k - a
30            # Only valid if there are enough people on the right side.
31            if b <= right:
32                # comb(left, a) * comb(right, b) ways to pick the two subsets,
33                # multiplied by 2 to account for the symmetric arrangement.
34                ans += 2 * comb(left, a) * comb(right, b)
35                ans %= MOD
36        return ans
37
1class Solution {
2    // Maximum size for precomputed factorial arrays
3    private static final int MAX_N = 100001;
4    // Modulo value for preventing integer overflow (1e9 + 7)
5    private static final int MOD = (int) 1e9 + 7;
6    // factorial[i] stores i! % MOD
7    private static final long[] factorial = new long[MAX_N];
8    // invFactorial[i] stores the modular inverse of i! % MOD
9    private static final long[] invFactorial = new long[MAX_N];
10
11    // Static initializer: precompute factorials and their modular inverses
12    static {
13        factorial[0] = 1;
14        invFactorial[0] = 1;
15        for (int i = 1; i < MAX_N; ++i) {
16            // factorial[i] = i! = (i-1)! * i
17            factorial[i] = factorial[i - 1] * i % MOD;
18            // Modular inverse of i! via Fermat's little theorem: (i!)^(MOD-2)
19            invFactorial[i] = qmi(factorial[i], MOD - 2, MOD);
20        }
21    }
22
23    /**
24     * Fast exponentiation (quick power) under modulo.
25     * Computes (base^exponent) % mod in O(log exponent) time.
26     *
27     * @param base     the base value
28     * @param exponent the exponent
29     * @param mod      the modulo
30     * @return (base^exponent) % mod
31     */
32    public static long qmi(long base, long exponent, long mod) {
33        long result = 1;
34        while (exponent != 0) {
35            // If the current lowest bit is 1, multiply result by current base
36            if ((exponent & 1) == 1) {
37                result = result * base % mod;
38            }
39            exponent >>= 1;          // Move to the next bit
40            base = base * base % mod; // Square the base for the next bit position
41        }
42        return result;
43    }
44
45    /**
46     * Computes the binomial coefficient C(n, k) = n! / (k! * (n-k)!) under modulo.
47     *
48     * @param n total number of items
49     * @param k number of items to choose
50     * @return C(n, k) % MOD
51     */
52    public static long comb(int n, int k) {
53        // C(n, k) = n! * inverse(k!) * inverse((n-k)!)
54        return (factorial[n] * invFactorial[k] % MOD) * invFactorial[n - k] % MOD;
55    }
56
57    /**
58     * Counts the number of arrangements based on choosing elements from
59     * the left and right sides of the given position.
60     *
61     * @param n   total number of elements
62     * @param pos the reference position (0-indexed)
63     * @param k   the number of elements to select in total
64     * @return the count modulo MOD
65     */
66    public int countVisiblePeople(int n, int pos, int k) {
67        int leftCount = pos;            // Number of available elements on the left side
68        int rightCount = n - pos - 1;   // Number of available elements on the right side
69        long answer = 0;
70
71        // Iterate over how many elements 'a' to pick from the left side
72        for (int a = 0; a <= Math.min(k, leftCount); ++a) {
73            int b = k - a; // Remaining elements 'b' to pick from the right side
74
75            // Only valid if we have enough elements on the right side
76            if (b <= rightCount) {
77                // Multiply by 2 to account for symmetric (mirror) arrangements
78                answer = (answer + 2 * comb(leftCount, a) % MOD * comb(rightCount, b) % MOD) % MOD;
79            }
80        }
81        return (int) answer;
82    }
83}
84
1// Maximum size for precomputed factorial tables.
2const int kMaxN = 100001;
3// Modulus used for all arithmetic (a large prime).
4const int kMod = 1e9 + 7;
5
6// Precomputed factorials:        factorial[i]        = i! % kMod
7// Precomputed inverse factorials: invFactorial[i]    = (i!)^(-1) % kMod
8long long factorial[kMaxN];
9long long invFactorial[kMaxN];
10
11// Fast modular exponentiation: computes (base^exponent) % mod.
12long long qpow(long long base, long long exponent, long long mod) {
13    long long result = 1;
14    while (exponent != 0) {
15        // If the current lowest bit is set, multiply the result by base.
16        if ((exponent & 1) == 1) {
17            result = result * base % mod;
18        }
19        exponent >>= 1;                 // Move to the next bit.
20        base = base * base % mod;        // Square the base for the next bit.
21    }
22    return result;
23}
24
25// Static initializer that fills the factorial and inverse-factorial tables
26// once, before any Solution method is used.
27int initTables = []() {
28    factorial[0] = 1;
29    invFactorial[0] = 1;
30    for (int i = 1; i < kMaxN; ++i) {
31        // factorial[i] = factorial[i - 1] * i
32        factorial[i] = factorial[i - 1] * i % kMod;
33        // Inverse factorial via Fermat's little theorem:
34        // (i!)^(-1) = (i!)^(kMod - 2) % kMod, since kMod is prime.
35        invFactorial[i] = qpow(factorial[i], kMod - 2, kMod);
36    }
37    return 0;
38}();
39
40// Combination C(n, k) = n! / (k! * (n - k)!) computed under the modulus.
41long long comb(int n, int k) {
42    return factorial[n] * invFactorial[k] % kMod * invFactorial[n - k] % kMod;
43}
44
45class Solution {
46public:
47    int countVisiblePeople(int n, int pos, int k) {
48        // Number of people available on the left and right of pos.
49        int left = pos;
50        int right = n - pos - 1;
51        long long ans = 0;
52
53        // Choose 'a' people from the left and the remaining 'b = k - a'
54        // from the right. The factor 2 accounts for the symmetry of
55        // arranging the two chosen groups.
56        for (int a = 0; a <= min(k, left); ++a) {
57            int b = k - a;
58            if (b <= right) {
59                ans = (ans + 2 * comb(left, a) % kMod * comb(right, b) % kMod) % kMod;
60            }
61        }
62        return ans;
63    }
64};
65
1// Maximum size for precomputed factorial arrays
2const MAX_SIZE = 100001;
3
4// Modulus for preventing integer overflow (a large prime number)
5const MOD = 1000000007n;
6
7// factorials[i] stores i! mod MOD
8const factorials: bigint[] = Array(MAX_SIZE).fill(0n);
9
10// inverseFactorials[i] stores the modular inverse of i! mod MOD
11const inverseFactorials: bigint[] = Array(MAX_SIZE).fill(0n);
12
13/**
14 * Fast exponentiation: computes (base^exponent) mod modulus.
15 * Uses binary exponentiation for O(log exponent) time complexity.
16 *
17 * @param base - The base value
18 * @param exponent - The exponent value
19 * @param modulus - The modulus value
20 * @returns (base^exponent) mod modulus
21 */
22function qmi(base: bigint, exponent: bigint, modulus: bigint): bigint {
23    let result = 1n;
24    while (exponent > 0n) {
25        // If the lowest bit is set, multiply the current base into the result
26        if (exponent & 1n) result = (result * base) % modulus;
27        // Shift exponent right by one bit
28        exponent >>= 1n;
29        // Square the base for the next bit position
30        base = (base * base) % modulus;
31    }
32    return result;
33}
34
35// Precompute factorials and their modular inverses.
36// 0! = 1 and its inverse is also 1.
37factorials[0] = 1n;
38inverseFactorials[0] = 1n;
39for (let i = 1; i < MAX_SIZE; i++) {
40    // i! = (i-1)! * i
41    factorials[i] = (factorials[i - 1] * BigInt(i)) % MOD;
42    // Modular inverse of i! using Fermat's Little Theorem:
43    // a^(MOD-2) ≡ a^(-1) (mod MOD) when MOD is prime
44    inverseFactorials[i] = qmi(factorials[i], MOD - 2n, MOD);
45}
46
47/**
48 * Computes the binomial coefficient C(n, k) mod MOD.
49 * Formula: C(n, k) = n! / (k! * (n-k)!)
50 * Using precomputed factorials and their inverses for O(1) lookup.
51 *
52 * @param n - Total number of items
53 * @param k - Number of items to choose
54 * @returns C(n, k) mod MOD
55 */
56function comb(n: number, k: number): bigint {
57    return (((factorials[n] * inverseFactorials[k]) % MOD) * inverseFactorials[n - k]) % MOD;
58}
59
60/**
61 * Counts the number of valid arrangements where exactly k people are visible
62 * given a person positioned at index `pos` in a line of `n` people.
63 *
64 * The line is split into a left segment (l people) and a right segment (r people).
65 * We distribute the k visible people across both sides, choosing `a` from the left
66 * and `b = k - a` from the right. The factor of 2 accounts for symmetry.
67 *
68 * @param n - Total number of people in the line
69 * @param pos - The position index of the reference person
70 * @param k - The exact number of visible people required
71 * @returns The total count of valid arrangements mod MOD (as a number)
72 */
73function countVisiblePeople(n: number, pos: number, k: number): number {
74    // Number of people to the left of the reference position
75    const l = pos;
76    // Number of people to the right of the reference position
77    const r = n - pos - 1;
78    let ans = 0n;
79
80    // Iterate over how many of the k visible people come from the left side
81    for (let a = 0; a <= Math.min(k, l); a++) {
82        // The remaining visible people must come from the right side
83        const b = k - a;
84        // Only valid if the right side has enough people
85        if (b <= r) {
86            // Multiply choices from both sides; factor of 2 for symmetry
87            ans = (ans + ((((2n * comb(l, a)) % MOD) * comb(r, b)) % MOD)) % MOD;
88        }
89    }
90
91    return Number(ans);
92}
93

Time and Space Complexity

  • Time Complexity: O(n). The precomputation phase dominates the cost: the loop building the factorial array f and the modular inverse array g runs N - 1 times, where N = 100001. Within each iteration, the call to pow(f[i], MOD - 2, MOD) performs fast exponentiation, which costs O(log MOD) time; since MOD is a fixed constant, this factor is treated as a constant. Thus the precomputation is O(n) overall, where n is the input integer n (bounded by N). The subsequent countVisiblePeople method loops at most min(k, l) + 1 times, each iteration performing constant-time comb lookups, which is also O(n). Therefore, the total time complexity is O(n).

  • Space Complexity: O(n). Two arrays f and g, each of length N, are allocated to store the factorials and their modular inverses. This requires O(n) space, where n is the input integer n. No additional space that scales with the input is used elsewhere, so the overall space complexity is O(n).

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

Common Pitfalls

Pitfall 1: Precomputing inverse factorials with a per-element modular exponentiation (severe performance issue)

The most common and subtle pitfall in this solution is how the inverse factorials are computed. The code calls pow(fact[i], MOD - 2, MOD) inside the precomputation loop:

for i in range(1, MAX_N):
    fact[i] = fact[i - 1] * i % MOD
    inv_fact[i] = pow(fact[i], MOD - 2, MOD)   # ← O(log MOD) every iteration

Each pow(..., MOD - 2, MOD) costs O(log MOD) modular multiplications. Doing this for all MAX_N = 100001 entries gives O(N log MOD) total work for the precomputation — roughly 3 million extra multiplications that are completely avoidable. For tighter time limits or larger N, this can cause a Time Limit Exceeded.

Solution: Compute only the last inverse factorial with Fermat's Little Theorem, then build the rest backward in O(1) per step using the identity:

inv_fact[i - 1] = inv_fact[i] * i

This works because (i-1)!^{-1} = (i!)^{-1} · i.

fact = [1] * MAX_N
inv_fact = [1] * MAX_N
for i in range(1, MAX_N):
    fact[i] = fact[i - 1] * i % MOD

# One modular exponentiation only:
inv_fact[MAX_N - 1] = pow(fact[MAX_N - 1], MOD - 2, MOD)
for i in range(MAX_N - 1, 0, -1):
    inv_fact[i - 1] = inv_fact[i] * i % MOD

This reduces the precomputation from O(N log MOD) to O(N + log MOD).


Pitfall 2: Forgetting the b <= right validity check (out-of-range / wrong answer)

If you drop the if b <= right guard and call comb(right, b) with b > right, then comb computes inv_fact[right - b] with a negative index. In Python a negative index silently wraps around to a valid array position, so you get no error but a wrong answer — one of the hardest bugs to spot.

Solution: Always validate both ends of the split before calling comb. Make comb itself defensive:

def comb(n: int, k: int) -> int:
    if k < 0 or k > n:
        return 0
    return fact[n] * inv_fact[k] * inv_fact[n - k] % MOD

With this guard, even a missing range check produces the correct 0 contribution instead of a silent wraparound.


Pitfall 3: Misinterpreting the factor of 2 (logic error)

It's tempting to think the person at pos must face a particular way, or to forget them entirely. In reality, the person at pos does not see themselves, and their own direction choice ('L' or 'R') has no effect on visibility. Therefore each valid assignment of the other n - 1 people corresponds to two total assignments.

Solution: Keep the * 2 multiplier and remember its meaning: it represents the free, independent choice of the person at pos. A quick sanity check — when k ranges over all valid values, the summed answers should equal 2^n (every direction assignment is counted exactly once), since 2 · sum_a C(l, a) · C(r, k-a) = 2 · 2^{l+r} = 2 · 2^{n-1} = 2^n.


Pitfall 4: Sizing MAX_N too small

comb indexes fact and inv_fact up to left or right, which can be as large as n - 1. If MAX_N is set just below the maximum possible n, you'll get an IndexError.

Solution: Ensure MAX_N is strictly greater than the maximum possible value of n from the constraints. Setting MAX_N = 100001 safely covers n up to 100000.

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:

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More