3881. Direction Assignments with Exactly K Visible People
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 ofpos) is visible if and only if they choose'L'. - A person
i > pos(standing to the right ofpos) 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.
How We Pick the Algorithm
Why Math / Bit Manipulation?
This problem maps to Math / Bit Manipulation through a short path in the full flowchart.
The problem counts direction assignments using combinatorial enumeration and binomial coefficients, a pure counting/math approach.
Open in FlowchartIntuition
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 = pospeople to the left. - There are
r = n - pos - 1people 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
aof thelleft people are facing'L'(visible), while the remainingl - aface'R'. The number of ways to choose them isC(l, a). - Similarly, we pick which
b = k - aof therright people are facing'R'(visible), with the rest facing'L'. The number of ways isC(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]storesi!moduloMOD.g[i]stores the modular inverse ofi!, i.e.(i!)^{-1}moduloMOD.
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 = posis the number of people on the left.r = n - pos - 1is 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, whereNincludes the cost of one modular exponentiation per index). Each query loops at mostmin(k, l) + 1times with constant work per iteration, so each call runs inO(min(k, pos))time. - Space complexity:
O(N)for the two precomputed arraysfandg.
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 (index0).r = n - pos - 1 = 4 - 1 - 1 = 2— there are 2 people on the right (indices2and3).
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 frompos), only 1 way.comb(r, b) = C(2, 1) = 2— choose which of the 2 right people faces'R'(towardpos); 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'(towardpos), only 1 way.comb(r, b) = C(2, 0) = 1— both right people must face'L'(away frompos), 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 pos | Count |
|---|---|---|---|
R | R, L | idx 2 | 1 ✅ |
R | L, R | idx 3 | 1 ✅ |
L | L, L | idx 0 | 1 ✅ |
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
371class 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}
841// 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};
651// 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}
93Time and Space Complexity
-
Time Complexity:
O(n). The precomputation phase dominates the cost: the loop building the factorial arrayfand the modular inverse arraygrunsN - 1times, whereN = 100001. Within each iteration, the call topow(f[i], MOD - 2, MOD)performs fast exponentiation, which costsO(log MOD)time; sinceMODis a fixed constant, this factor is treated as a constant. Thus the precomputation isO(n)overall, wherenis the input integern(bounded byN). The subsequentcountVisiblePeoplemethod loops at mostmin(k, l) + 1times, each iteration performing constant-timecomblookups, which is alsoO(n). Therefore, the total time complexity isO(n). -
Space Complexity:
O(n). Two arraysfandg, each of lengthN, are allocated to store the factorials and their modular inverses. This requiresO(n)space, wherenis the input integern. No additional space that scales with the input is used elsewhere, so the overall space complexity isO(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 RoadmapHow many times is a tree node visited in a depth first search?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Backtracking Algorithm Prereq DFS with States problems dfs_with_states If you prefer videos here's a video that explains backtracking in a fun and easy way Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of the problem Feel free to go back to
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!