2954. Count the Number of Infection Sequences
Problem Description
You have a line of n
people where some are initially infected (sick). The positions of initially infected people are given in the array sick
(sorted in increasing order).
The infection spreads step by step: at each step, exactly one uninfected person who is adjacent to an infected person becomes infected. This process continues until everyone in the line is infected.
An infection sequence represents the order in which the uninfected people become infected (not including those who were initially infected).
Your task is to find the total number of different possible infection sequences, returning the answer modulo 10^9 + 7
.
For example, if you have people at positions 0, 1, 2, 3, 4 and positions 1 and 3 are initially sick, then positions 0, 2, and 4 need to get infected. The infection can spread in different orders:
- Position 2 could get infected first (from position 1 or 3), then position 0 (from position 1), then position 4 (from position 3)
- Position 0 could get infected first (from position 1), then position 2 (from position 1 or 3), then position 4 (from position 3)
- And several other possible sequences
The key insight is that infected people divide the uninfected into separate segments. Within each segment, infection can spread from either end (left or right), except for the leftmost and rightmost segments which can only be infected from one direction. The total number of sequences depends on:
- How we interleave the infections across different segments (combinatorial arrangement)
- For each middle segment, the number of ways infection can spread within it (which is
2^(length-1)
since at each step we can choose to infect from left or right end)
Intuition
Let's think about how the infection spreads. The initially sick people create "barriers" that divide the healthy people into separate segments. For instance, if we have 10 people and positions 3 and 7 are sick, we get three segments: [0,1,2], [4,5,6], and [8,9].
The key observation is that people in different segments can get infected in an interleaved manner. If segment A has 3 people to infect and segment B has 2 people, we need to arrange these 5 infections in some order - this is like arranging 5 items where 3 are of type A and 2 are of type B. The number of such arrangements is 5!/(3! × 2!)
, which is the multinomial coefficient.
For the general case with multiple segments having nums[0], nums[1], ..., nums[k]
healthy people, the total number of ways to interleave their infections is:
s! / (nums[0]! × nums[1]! × ... × nums[k]!)
where s
is the total number of healthy people.
But wait, there's more! Within each segment, the infection can spread in different ways. Consider a middle segment (not at the ends) with 4 healthy people. The infection can come from both sides. At each step, we choose whether to infect from the left or right end:
- First infection: choose left or right (2 choices)
- Second infection: choose left or right (2 choices)
- Third infection: choose left or right (2 choices)
- Fourth infection: only one person left (no choice)
This gives us 2^3 = 2^(4-1)
ways for a segment of length 4. Generally, a middle segment of length x
has 2^(x-1)
infection patterns.
However, the leftmost and rightmost segments are special - they can only be infected from one side (the side with the sick person), so they each have only 1 way to be infected.
Combining both ideas:
- The multinomial coefficient for interleaving:
s! / ∏(nums[i]!)
- The infection patterns within middle segments:
∏(2^(nums[i]-1))
for middle segments only
The final answer is the product of these two values, taken modulo 10^9 + 7
.
Learn more about Math and Combinatorics patterns.
Solution Approach
The implementation follows the mathematical formula we derived:
(s! / ∏(nums[i]!)) × ∏(2^(nums[i]-1))
for middle segments
Step 1: Precompute Factorials
mod = 10**9 + 7
mx = 10**5
fac = [1] * (mx + 1)
for i in range(2, mx + 1):
fac[i] = fac[i - 1] * i % mod
We precompute factorials up to 10^5
since that's the maximum possible value of n
. This allows us to quickly access k!
for any k
.
Step 2: Calculate Segment Sizes
nums = [b - a - 1 for a, b in pairwise([-1] + sick + [n])]
This clever line calculates the size of each healthy segment. By adding -1
at the start and n
at the end to the sick
array, we can use pairwise
to get consecutive pairs and calculate the gaps between them. For example, if sick = [3, 7]
and n = 10
, we get pairs (-1, 3)
, (3, 7)
, (7, 10)
, giving us segments of size 3-(-1)-1 = 3
, 7-3-1 = 3
, 10-7-1 = 2
.
Step 3: Calculate the Multinomial Coefficient
s = sum(nums)
ans = fac[s]
for x in nums:
if x:
ans = ans * pow(fac[x], mod - 2, mod) % mod
We start with s!
(total permutations) and divide by each nums[i]!
. Since we're working modulo a prime, division is done using the modular multiplicative inverse: a/b ≡ a × b^(-1) ≡ a × b^(mod-2)
(by Fermat's Little Theorem).
Step 4: Account for Internal Segment Choices
for x in nums[1:-1]:
if x > 1:
ans = ans * pow(2, x - 1, mod) % mod
For each middle segment (excluding first and last segments, hence nums[1:-1]
), we multiply by 2^(x-1)
to account for the different ways infection can spread within that segment. We only do this when x > 1
because a segment of size 0 or 1 has only one way to be infected.
Key Implementation Details:
- Modular Arithmetic: All operations are done modulo
10^9 + 7
to prevent overflow - Modular Inverse: Using Fermat's Little Theorem,
a^(-1) ≡ a^(p-2) (mod p)
whenp
is prime - Fast Power: The
pow(base, exp, mod)
function efficiently computesbase^exp % mod
The time complexity is O(n)
for preprocessing factorials and O(k)
for the main computation where k
is the number of segments, giving us O(n)
overall.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Example: n = 7
people at positions [0, 1, 2, 3, 4, 5, 6]
, with sick = [1, 4]
(positions 1 and 4 are initially infected).
Step 1: Identify the healthy segments
The sick people at positions 1 and 4 divide the line into segments:
- Segment 0: Position
[0]
- leftmost segment (1 person) - Segment 1: Positions
[2, 3]
- middle segment (2 people) - Segment 2: Positions
[5, 6]
- rightmost segment (2 people)
Using the formula: nums = [b - a - 1 for a, b in pairwise([-1] + sick + [n])]
- Pairs:
(-1, 1)
,(1, 4)
,(4, 7)
- Segment sizes:
[1-(-1)-1 = 1, 4-1-1 = 2, 7-4-1 = 2]
- So
nums = [1, 2, 2]
Step 2: Calculate the multinomial coefficient
Total healthy people: s = 1 + 2 + 2 = 5
The multinomial coefficient represents how we can interleave the infections:
5! / (1! × 2! × 2!) = 120 / (1 × 2 × 2) = 30
This means there are 30 ways to arrange 5 infections where 1 comes from segment 0, 2 from segment 1, and 2 from segment 2.
Step 3: Account for internal choices in middle segments
- Segment 0 (leftmost): Can only be infected from the right (position 1), so 1 way
- Segment 1 (middle): Has 2 people, can be infected from both sides
- Can infect position 2 first (from position 1), then position 3 (from position 4)
- OR infect position 3 first (from position 4), then position 2 (from position 1)
- Total:
2^(2-1) = 2
ways
- Segment 2 (rightmost): Can only be infected from the left (position 4), so 1 way
Step 4: Combine the results
Total infection sequences = (multinomial coefficient) × (internal choices for middle segments)
= 30 × 2 = 60
Verification with a few sequences: Here are some of the 60 possible infection sequences (showing who gets infected at each step):
[0, 2, 3, 5, 6]
- leftmost first, then middle left-to-right, then rightmost[0, 3, 2, 5, 6]
- leftmost first, then middle right-to-left, then rightmost[2, 0, 5, 3, 6]
- middle-left, leftmost, rightmost-left, middle-right, rightmost-right[5, 2, 6, 0, 3]
- rightmost-left, middle-left, rightmost-right, leftmost, middle-right
Each represents a valid infection sequence where at every step, exactly one healthy person adjacent to an infected person becomes infected.
The final answer would be 60 % (10^9 + 7) = 60
.
Solution Implementation
1from typing import List
2
3# Constants for modular arithmetic
4MOD = 10**9 + 7
5MAX_N = 10**5
6
7# Precompute factorials for combinatorial calculations
8factorial = [1] * (MAX_N + 1)
9for i in range(2, MAX_N + 1):
10 factorial[i] = factorial[i - 1] * i % MOD
11
12
13class Solution:
14 def numberOfSequence(self, n: int, sick: List[int]) -> int:
15 """
16 Calculate the number of valid infection sequences.
17
18 Args:
19 n: Total number of positions
20 sick: List of initially sick positions (0-indexed)
21
22 Returns:
23 Number of valid sequences modulo 10^9 + 7
24 """
25 # Calculate gaps between consecutive sick positions
26 # Add boundaries at -1 and n to handle edge cases
27 extended_sick = [-1] + sick + [n]
28 gaps = []
29 for i in range(len(extended_sick) - 1):
30 gap_size = extended_sick[i + 1] - extended_sick[i] - 1
31 gaps.append(gap_size)
32
33 # Total number of healthy positions to be infected
34 total_healthy = sum(gaps)
35
36 # Start with total permutations of all healthy positions
37 result = factorial[total_healthy]
38
39 # Divide by factorial of each gap size (multinomial coefficient)
40 # This accounts for the ordering within each gap
41 for gap_size in gaps:
42 if gap_size > 0:
43 # Compute modular inverse using Fermat's little theorem
44 inverse = pow(factorial[gap_size], MOD - 2, MOD)
45 result = result * inverse % MOD
46
47 # For internal gaps (not at boundaries), multiply by 2^(gap_size-1)
48 # This accounts for the bidirectional spread within each gap
49 for gap_size in gaps[1:-1]: # Exclude first and last gaps
50 if gap_size > 1:
51 result = result * pow(2, gap_size - 1, MOD) % MOD
52
53 return result
54
1class Solution {
2 // Constants for modular arithmetic
3 private static final int MODULO = (int) (1e9 + 7);
4 private static final int MAX_SIZE = 100000;
5
6 // Pre-computed factorial values for efficiency
7 private static final int[] FACTORIAL = new int[MAX_SIZE + 1];
8
9 // Static block to initialize factorial array
10 static {
11 FACTORIAL[0] = 1;
12 for (int i = 1; i <= MAX_SIZE; i++) {
13 FACTORIAL[i] = (int) ((long) FACTORIAL[i - 1] * i % MODULO);
14 }
15 }
16
17 public int numberOfSequence(int n, int[] sick) {
18 int sickCount = sick.length;
19
20 // Calculate the number of healthy people in each segment
21 // segments[0]: healthy people before first sick person
22 // segments[i]: healthy people between sick[i-1] and sick[i]
23 // segments[m]: healthy people after last sick person
24 int[] healthySegments = new int[sickCount + 1];
25 healthySegments[0] = sick[0];
26 healthySegments[sickCount] = n - sick[sickCount - 1] - 1;
27 for (int i = 1; i < sickCount; i++) {
28 healthySegments[i] = sick[i] - sick[i - 1] - 1;
29 }
30
31 // Calculate total number of healthy people
32 int totalHealthy = 0;
33 for (int segmentSize : healthySegments) {
34 totalHealthy += segmentSize;
35 }
36
37 // Start with factorial of total healthy people (all permutations)
38 int result = FACTORIAL[totalHealthy];
39
40 // Divide by factorial of each segment size (multinomial coefficient)
41 // This accounts for the fact that within each segment, the order matters less
42 for (int segmentSize : healthySegments) {
43 if (segmentSize > 0) {
44 // Using Fermat's Little Theorem: a^(-1) ≡ a^(p-2) (mod p) when p is prime
45 result = (int) ((long) result * modularPower(FACTORIAL[segmentSize], MODULO - 2) % MODULO);
46 }
47 }
48
49 // For internal segments (not first or last), multiply by 2^(size-1)
50 // This represents the different ways infection can spread from both sides
51 for (int i = 1; i < healthySegments.length - 1; i++) {
52 if (healthySegments[i] > 1) {
53 result = (int) ((long) result * modularPower(2, healthySegments[i] - 1) % MODULO);
54 }
55 }
56
57 return result;
58 }
59
60 /**
61 * Computes (base^exponent) % MODULO using fast exponentiation
62 * @param base the base number
63 * @param exponent the exponent
64 * @return (base^exponent) % MODULO
65 */
66 private int modularPower(long base, long exponent) {
67 long result = 1;
68
69 // Binary exponentiation algorithm
70 while (exponent > 0) {
71 // If current bit is 1, multiply result by base
72 if ((exponent & 1) == 1) {
73 result = result * base % MODULO;
74 }
75 // Square the base for next bit
76 base = base * base % MODULO;
77 // Move to next bit
78 exponent >>= 1;
79 }
80
81 return (int) result;
82 }
83}
84
1const int MAX_N = 1e5;
2const int MOD = 1e9 + 7;
3int factorial[MAX_N + 1];
4
5// Initialize factorial array at compile time
6auto initialize = [] {
7 factorial[0] = 1;
8 for (int i = 1; i <= MAX_N; ++i) {
9 factorial[i] = (1LL * factorial[i - 1] * i) % MOD;
10 }
11 return 0;
12}();
13
14// Fast exponentiation with modulo
15int modularPower(long long base, long long exponent) {
16 long long result = 1;
17 while (exponent > 0) {
18 if (exponent & 1) {
19 result = (result * base) % MOD;
20 }
21 base = (base * base) % MOD;
22 exponent >>= 1;
23 }
24 return result;
25}
26
27class Solution {
28public:
29 int numberOfSequence(int n, vector<int>& sick) {
30 int numSick = sick.size();
31
32 // Calculate gaps between sick positions
33 vector<int> gaps(numSick + 1);
34
35 // Gap before first sick position
36 gaps[0] = sick[0];
37
38 // Gap after last sick position
39 gaps[numSick] = n - sick[numSick - 1] - 1;
40
41 // Gaps between consecutive sick positions
42 for (int i = 1; i < numSick; i++) {
43 gaps[i] = sick[i] - sick[i - 1] - 1;
44 }
45
46 // Total number of healthy positions
47 int totalHealthy = accumulate(gaps.begin(), gaps.end(), 0);
48
49 // Start with factorial of total healthy positions (all permutations)
50 long long answer = factorial[totalHealthy];
51
52 // Divide by factorial of each gap size (multinomial coefficient)
53 for (int gapSize : gaps) {
54 if (gapSize > 0) {
55 // Multiply by modular inverse of factorial[gapSize]
56 answer = (answer * modularPower(factorial[gapSize], MOD - 2)) % MOD;
57 }
58 }
59
60 // For internal gaps, multiply by 2^(gapSize-1) for ordering choices
61 for (int i = 1; i < gaps.size() - 1; ++i) {
62 if (gaps[i] > 1) {
63 answer = (answer * modularPower(2, gaps[i] - 1)) % MOD;
64 }
65 }
66
67 return answer;
68 }
69};
70
1// Maximum value for factorial precomputation
2const MAX_SIZE = 1e5;
3// Modulo value for calculations (10^9 + 7)
4const MODULO: bigint = BigInt(1e9 + 7);
5// Array to store precomputed factorials
6const factorials: bigint[] = Array(MAX_SIZE + 1);
7
8// Initialize factorials array using IIFE (Immediately Invoked Function Expression)
9const initialize = (() => {
10 factorials[0] = 1n;
11 for (let i = 1; i <= MAX_SIZE; ++i) {
12 factorials[i] = (factorials[i - 1] * BigInt(i)) % MODULO;
13 }
14 return 0;
15})();
16
17/**
18 * Calculates base^exponent mod MODULO using fast exponentiation
19 * @param base - The base number as bigint
20 * @param exponent - The exponent as number
21 * @returns The result of (base^exponent) mod MODULO
22 */
23function qpow(base: bigint, exponent: number): bigint {
24 let result = 1n;
25
26 // Binary exponentiation algorithm
27 while (exponent > 0) {
28 // If current bit is 1, multiply result by base
29 if (exponent & 1) {
30 result = (result * base) % MODULO;
31 }
32 // Square the base for next bit
33 base = (base * base) % MODULO;
34 // Shift exponent right by 1 bit
35 exponent >>= 1;
36 }
37
38 return result;
39}
40
41/**
42 * Calculates the number of possible infection sequences
43 * @param n - Total number of children
44 * @param sick - Array of indices of initially sick children (sorted)
45 * @returns Number of possible sequences modulo 10^9 + 7
46 */
47function numberOfSequence(n: number, sick: number[]): number {
48 const sickCount = sick.length;
49 // Array to store gaps between sick children
50 const gapSizes: number[] = Array(sickCount + 1);
51
52 // Calculate gap before first sick child
53 gapSizes[0] = sick[0];
54
55 // Calculate gap after last sick child
56 gapSizes[sickCount] = n - sick[sickCount - 1] - 1;
57
58 // Calculate gaps between consecutive sick children
59 for (let i = 1; i < sickCount; i++) {
60 gapSizes[i] = sick[i] - sick[i - 1] - 1;
61 }
62
63 // Calculate total number of healthy children
64 const totalHealthy = gapSizes.reduce((sum, gap) => sum + gap, 0);
65
66 // Start with factorial of total healthy children (multinomial coefficient numerator)
67 let answer = factorials[totalHealthy];
68
69 // Divide by factorial of each gap size (multinomial coefficient denominator)
70 for (let gapSize of gapSizes) {
71 if (gapSize > 0) {
72 // Multiply by modular inverse of factorial[gapSize]
73 answer = (answer * qpow(factorials[gapSize], Number(MODULO) - 2)) % MODULO;
74 }
75 }
76
77 // For internal gaps, multiply by 2^(gapSize-1) for infection direction choices
78 for (let i = 1; i < gapSizes.length - 1; ++i) {
79 if (gapSizes[i] > 1) {
80 answer = (answer * qpow(2n, gapSizes[i] - 1)) % MODULO;
81 }
82 }
83
84 return Number(answer);
85}
86
Time and Space Complexity
Time Complexity: O(m)
, where m
is the length of the array sick
.
The algorithm performs the following operations:
- Creating the
nums
array usingpairwise
iteration:O(m)
- Computing the sum of
nums
:O(m)
- First loop iterating through
nums
for modular inverse calculations:O(m)
, where eachpow(fac[x], mod - 2, mod)
operation takesO(log(mod))
time - Second loop iterating through
nums[1:-1]
for power calculations:O(m)
, where eachpow(2, x - 1, mod)
operation takesO(log(x))
time
Since mod
is a constant (10^9 + 7
) and x
is bounded by n
, the overall time complexity is O(m)
.
Space Complexity: O(m)
(excluding the preprocessing factorial array)
The algorithm uses:
nums
array which storesm + 1
elements:O(m)
- A few scalar variables (
ans
,s
):O(1)
The preprocessing factorial array fac
of size mx + 1
is not counted as it's computed outside the solution class. Therefore, the space complexity is O(m)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Modular Division
One of the most common mistakes is attempting direct division in modular arithmetic:
# WRONG - This doesn't work with modular arithmetic result = (factorial[total] // factorial[gap_size]) % MOD
Why it fails: Division doesn't distribute over modulo operation. (a/b) % p ≠ (a % p) / (b % p)
.
Solution: Use modular multiplicative inverse with Fermat's Little Theorem:
# CORRECT - Using modular inverse
inverse = pow(factorial[gap_size], MOD - 2, MOD)
result = result * inverse % MOD
2. Off-by-One Errors in Gap Calculation
A frequent mistake is incorrectly calculating the gap sizes between sick positions:
# WRONG - Forgets to subtract 1 for the sick positions themselves
gaps = [extended_sick[i + 1] - extended_sick[i] for i in range(len(extended_sick) - 1)]
# WRONG - Incorrect boundary handling
gaps = [sick[i + 1] - sick[i] - 1 for i in range(len(sick) - 1)] # Misses edge gaps
Solution: Always subtract 1 to exclude the sick positions and properly handle boundaries:
# CORRECT
extended_sick = [-1] + sick + [n]
gaps = [extended_sick[i + 1] - extended_sick[i] - 1 for i in range(len(extended_sick) - 1)]
3. Applying 2^(gap-1) to Boundary Segments
A critical error is multiplying by 2^(gap-1)
for ALL segments, including those at the boundaries:
# WRONG - Applies to all gaps including boundaries
for gap_size in gaps:
if gap_size > 1:
result = result * pow(2, gap_size - 1, MOD) % MOD
Why it fails: Boundary segments (leftmost and rightmost) can only be infected from one direction, not both.
Solution: Only apply to internal segments:
# CORRECT - Skip first and last gaps
for gap_size in gaps[1:-1]:
if gap_size > 1:
result = result * pow(2, gap_size - 1, MOD) % MOD
4. Integer Overflow Without Modulo
Forgetting to apply modulo at each step can cause overflow:
# WRONG - Can overflow before taking modulo
result = factorial[total]
for gap_size in gaps:
result = result * pow(factorial[gap_size], MOD - 2, MOD) # Missing % MOD
result = result % MOD # Too late!
Solution: Apply modulo after every multiplication:
# CORRECT result = factorial[total] for gap_size in gaps: result = result * inverse % MOD # Apply modulo immediately
5. Handling Empty Gaps Incorrectly
When two sick positions are adjacent (gap size = 0), special care is needed:
# WRONG - Division by factorial[0] without checking
for gap_size in gaps:
result = result * pow(factorial[gap_size], MOD - 2, MOD) % MOD
Solution: Skip gaps of size 0:
# CORRECT
for gap_size in gaps:
if gap_size > 0: # Only process non-empty gaps
result = result * pow(factorial[gap_size], MOD - 2, MOD) % MOD
How does quick sort divide the problem into subproblems?
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 Template Prereq DFS with States problems dfs_with_states 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
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!