3405. Count the Number of Arrays with K Matching Adjacent Elements
Problem Description
You need to create arrays of size n
where each element is an integer between 1 and m
(inclusive). However, these arrays must satisfy a specific condition about consecutive equal elements.
A good array must meet these requirements:
- It has exactly
n
elements - Each element is in the range
[1, m]
- There are exactly
k
positions where an element equals its previous element
More specifically, when checking pairs of consecutive elements (arr[i-1], arr[i])
for i
from 1 to n-1
, exactly k
of these pairs should have equal elements.
For example:
- If
n=4
,m=2
,k=1
: The array[1,1,2,1]
would be good because there's exactly one position (index 1) wherearr[1] == arr[0]
- The array
[1,2,2,2]
would havek=2
since there are two positions where consecutive elements are equal
Your task is to count how many different good arrays can be formed with the given parameters. Since this number can be very large, return the result modulo 10^9 + 7
.
The problem essentially asks: Given the array length, the range of values, and the required number of consecutive equal pairs, how many valid arrays exist?
Intuition
Let's think about what it means to have exactly k
positions where consecutive elements are equal. If we visualize the array, we can think of it as having "breakpoints" and "segments".
When two consecutive elements are different, we can imagine there's a "break" between them. When consecutive elements are the same, they belong to the same "segment".
If we have k
positions where consecutive elements are equal, then we have n - 1 - k
positions where consecutive elements are different (since there are n - 1
total pairs of consecutive elements).
This means our array is divided into n - k
segments, where all elements within each segment are the same, but adjacent segments have different values.
For example, with array [2,2,2,1,1,3,3]
:
- We have segments:
[2,2,2]
,[1,1]
,[3,3]
- There are 4 positions with equal consecutive elements (positions 1,2,4,6)
- There are 2 breaks (between segments)
Now, how many ways can we create such an array?
Step 1: Where to place the breaks?
We need to choose which n - 1 - k
positions out of n - 1
possible positions will be "breaks". This is a combination problem: C(n-1, n-1-k)
which equals C(n-1, k)
.
Step 2: What values to assign to each segment?
- The first segment can be any value from
1
tom
, giving usm
choices - Each subsequent segment must be different from the previous one (to maintain the break), giving us
m - 1
choices for each - Since we have
n - k
segments total, we haven - k - 1
segments after the first one
Therefore, the total number of ways to assign values is: m × (m-1)^(n-k-1)
Combining both parts: C(n-1, k) × m × (m-1)^(n-k-1)
This formula captures the essence of constructing all possible good arrays by first deciding where segments break, then assigning valid values to each segment.
Learn more about Math and Combinatorics patterns.
Solution Approach
The implementation uses combinatorics with precomputed factorials and modular arithmetic to efficiently calculate the result.
Precomputation of Factorials and Inverses:
The code starts by precomputing factorials and their modular inverses up to a maximum value:
f[i]
storesi!
modulo10^9 + 7
g[i]
stores the modular inverse off[i]
mx = 10**5 + 10
mod = 10**9 + 7
f = [1] + [0] * mx # f[i] = i! mod (10^9 + 7)
g = [1] + [0] * mx # g[i] = inverse of f[i]
for i in range(1, mx):
f[i] = f[i - 1] * i % mod
g[i] = pow(f[i], mod - 2, mod) # Fermat's little theorem for inverse
The modular inverse is calculated using Fermat's Little Theorem: if p
is prime, then a^(p-2) ≡ a^(-1) (mod p)
.
Combination Function:
The combination function C(m, n)
is implemented using the precomputed factorials:
def comb(m: int, n: int) -> int:
return f[m] * g[n] * g[m - n] % mod
This calculates C(m, n) = m! / (n! × (m-n)!)
using modular arithmetic.
Main Solution:
The solution applies the formula we derived:
class Solution:
def countGoodArrays(self, n: int, m: int, k: int) -> int:
return comb(n - 1, k) * m * pow(m - 1, n - k - 1, mod) % mod
Breaking down the calculation:
comb(n - 1, k)
: Number of ways to choose whichk
positions have equal consecutive elementsm
: Number of choices for the first segment's valuepow(m - 1, n - k - 1, mod)
: Number of ways to assign values to the remainingn - k - 1
segments, where each must differ from the previous- All multiplications are done with modulo
10^9 + 7
to prevent overflow
The use of fast exponentiation (pow
with three arguments) ensures efficient computation of large powers while maintaining the modular constraint.
Time Complexity:
- Precomputation:
O(mx)
wheremx = 10^5 + 10
- Per query:
O(log n)
for the power calculation - Overall:
O(mx + log n)
Space Complexity: O(mx)
for storing the factorial and inverse arrays.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with n=5
, m=3
, k=2
.
We want arrays of length 5, with values from 1 to 3, and exactly 2 positions where consecutive elements are equal.
Step 1: Understanding the structure
With k=2
positions having equal consecutive elements, we have:
- Total consecutive pairs:
n-1 = 4
- Positions with different consecutive elements:
4-2 = 2
- Number of segments:
n-k = 5-2 = 3
So our array will have 3 segments where values within each segment are the same, but adjacent segments differ.
Step 2: Choosing where to place the equal pairs
We need to choose which 2 positions out of 4 will have equal consecutive elements.
This is C(4,2) = 6
ways.
For example, if positions 1 and 3 have equal pairs:
- Position 0→1: equal (segment continues)
- Position 1→2: different (new segment)
- Position 2→3: equal (segment continues)
- Position 3→4: different (new segment)
This gives us the pattern: [a,a,b,b,c]
where a≠b and b≠c.
Step 3: Assigning values to segments
For our 3 segments:
- First segment: can be any of the 3 values (1, 2, or 3)
- Second segment: must differ from first, so 2 choices
- Third segment: must differ from second, so 2 choices
Total value assignments: 3 × 2 × 2 = 12
Step 4: Final calculation
Total good arrays = C(4,2) × 3 × 2² = 6 × 3 × 4 = 72
Using our formula: C(n-1,k) × m × (m-1)^(n-k-1)
= C(4,2) × 3 × 2²
= 6 × 3 × 4
= 72
Some example good arrays:
[1,1,2,2,3]
- segments: [1,1], [2,2], [3][2,2,1,1,3]
- segments: [2,2], [1,1], [3][3,1,1,2,2]
- segments: [3], [1,1], [2,2][1,1,1,2,3]
- segments: [1,1,1], [2], [3]
Each follows the pattern of having exactly 2 positions where consecutive elements are equal.
Solution Implementation
1# Constants for array size and modulo value
2MAX_SIZE = 10**5 + 10
3MOD = 10**9 + 7
4
5# Precompute factorials and their modular inverses
6factorial = [1] + [0] * MAX_SIZE
7factorial_inverse = [1] + [0] * MAX_SIZE
8
9for i in range(1, MAX_SIZE):
10 # Compute factorial[i] = i! mod MOD
11 factorial[i] = factorial[i - 1] * i % MOD
12 # Compute modular inverse of factorial[i] using Fermat's Little Theorem
13 # Since MOD is prime, inverse of a is a^(MOD-2) mod MOD
14 factorial_inverse[i] = pow(factorial[i], MOD - 2, MOD)
15
16
17def combination(total: int, choose: int) -> int:
18 """
19 Calculate C(total, choose) = total! / (choose! * (total-choose)!) mod MOD
20 Uses precomputed factorials and their modular inverses for efficiency
21
22 Args:
23 total: Total number of items
24 choose: Number of items to choose
25
26 Returns:
27 The combination value modulo MOD
28 """
29 return factorial[total] * factorial_inverse[choose] * factorial_inverse[total - choose] % MOD
30
31
32class Solution:
33 def countGoodArrays(self, n: int, m: int, k: int) -> int:
34 """
35 Count the number of good arrays of length n with values from 1 to m
36 where exactly k adjacent pairs have equal values
37
38 Args:
39 n: Length of the array
40 m: Maximum value in array (values range from 1 to m)
41 k: Number of adjacent pairs with equal values
42
43 Returns:
44 Number of good arrays modulo MOD
45 """
46 # Choose k positions out of (n-1) adjacent pairs to have equal values
47 positions = combination(n - 1, k)
48
49 # First element can be any of m values
50 first_element = m
51
52 # For the remaining (n-k-1) positions where values must differ from previous,
53 # each can be any of (m-1) values
54 remaining_positions = pow(m - 1, n - k - 1, MOD)
55
56 return positions * first_element * remaining_positions % MOD
57
1class Solution {
2 // Constants for array size and modulo value
3 private static final int MAX_SIZE = (int) 1e5 + 10;
4 private static final int MODULO = (int) 1e9 + 7;
5
6 // factorial[i] stores i! mod MODULO
7 private static final long[] factorial = new long[MAX_SIZE];
8 // inverseFactorial[i] stores the modular inverse of i! mod MODULO
9 private static final long[] inverseFactorial = new long[MAX_SIZE];
10
11 // Static initialization block to precompute factorials and their inverses
12 static {
13 factorial[0] = 1;
14 inverseFactorial[0] = 1;
15
16 // Compute factorials
17 for (int i = 1; i < MAX_SIZE; ++i) {
18 factorial[i] = factorial[i - 1] * i % MODULO;
19 // Compute modular inverse using Fermat's Little Theorem
20 // Since MODULO is prime, a^(-1) = a^(MODULO-2) mod MODULO
21 inverseFactorial[i] = qpow(factorial[i], MODULO - 2);
22 }
23 }
24
25 /**
26 * Computes (base^exponent) mod MODULO using fast exponentiation
27 * @param base the base number
28 * @param exponent the power to raise the base to
29 * @return (base^exponent) mod MODULO
30 */
31 public static long qpow(long base, int exponent) {
32 long result = 1;
33
34 while (exponent != 0) {
35 // If current bit is 1, multiply result by current base
36 if ((exponent & 1) == 1) {
37 result = result * base % MODULO;
38 }
39 // Square the base for next bit position
40 base = base * base % MODULO;
41 // Move to next bit
42 exponent >>= 1;
43 }
44
45 return result;
46 }
47
48 /**
49 * Computes binomial coefficient C(n, k) = n! / (k! * (n-k)!) mod MODULO
50 * @param n total number of items
51 * @param k number of items to choose
52 * @return C(n, k) mod MODULO
53 */
54 public static long comb(int n, int k) {
55 // C(n, k) = n! * (k!)^(-1) * ((n-k)!)^(-1) mod MODULO
56 return (int) (factorial[n] * inverseFactorial[k] % MODULO * inverseFactorial[n - k] % MODULO);
57 }
58
59 /**
60 * Counts the number of good arrays with given parameters
61 * @param n length of the array
62 * @param m range of values (1 to m)
63 * @param k number of pairs of adjacent equal elements
64 * @return count of good arrays modulo MODULO
65 */
66 public int countGoodArrays(int n, int m, int k) {
67 // Formula: C(n-1, k) * m * (m-1)^(n-k-1)
68 // - C(n-1, k): ways to choose positions for k equal adjacent pairs
69 // - m: choices for the first element
70 // - (m-1)^(n-k-1): choices for remaining positions (must differ from previous)
71 return (int) (comb(n - 1, k) * m % MODULO * qpow(m - 1, n - k - 1) % MODULO);
72 }
73}
74
1const int MAX_SIZE = 1e5 + 10;
2const int MOD = 1e9 + 7;
3
4// factorial[i] stores i! mod MOD
5long long factorial[MAX_SIZE];
6// inverseFactorial[i] stores (i!)^(-1) mod MOD
7long long inverseFactorial[MAX_SIZE];
8
9/**
10 * Fast exponentiation to calculate (base^exponent) mod MOD
11 * Uses binary exponentiation for O(log exponent) time complexity
12 */
13long long modularPower(long base, int exponent) {
14 long result = 1;
15 while (exponent != 0) {
16 // If current bit is 1, multiply result by base
17 if ((exponent & 1) == 1) {
18 result = result * base % MOD;
19 }
20 // Square the base for next bit
21 base = base * base % MOD;
22 // Move to next bit
23 exponent >>= 1;
24 }
25 return result;
26}
27
28// Pre-compute factorials and their modular inverses at compile time
29int precompute = []() {
30 // Base case: 0! = 1
31 factorial[0] = inverseFactorial[0] = 1;
32
33 // Compute all factorials up to MAX_SIZE
34 for (int i = 1; i < MAX_SIZE; ++i) {
35 factorial[i] = factorial[i - 1] * i % MOD;
36 // Use Fermat's Little Theorem: a^(-1) ≡ a^(p-2) (mod p) where p is prime
37 inverseFactorial[i] = modularPower(factorial[i], MOD - 2);
38 }
39 return 0;
40}();
41
42/**
43 * Calculate binomial coefficient C(totalItems, selectedItems) mod MOD
44 * Formula: C(m, n) = m! / (n! * (m-n)!)
45 */
46long long binomialCoefficient(int totalItems, int selectedItems) {
47 return factorial[totalItems] * inverseFactorial[selectedItems] % MOD *
48 inverseFactorial[totalItems - selectedItems] % MOD;
49}
50
51class Solution {
52public:
53 /**
54 * Count the number of good arrays of length n with exactly k equal adjacent pairs
55 * using m distinct values
56 *
57 * Formula: C(n-1, k) * m * (m-1)^(n-k-1)
58 * - C(n-1, k): choose k positions out of n-1 for equal adjacent pairs
59 * - m: choices for the first element
60 * - (m-1)^(n-k-1): choices for remaining positions to ensure they differ from previous
61 */
62 int countGoodArrays(int n, int m, int k) {
63 return binomialCoefficient(n - 1, k) * m % MOD *
64 modularPower(m - 1, n - k - 1) % MOD;
65 }
66};
67
1// Maximum array size constant
2const MAX_SIZE = 1e5 + 10;
3// Modulo value for calculations (10^9 + 7)
4const MODULO = BigInt(1e9 + 7);
5
6// Array to store factorial values: factorial[i] = i!
7const factorial: bigint[] = Array(MAX_SIZE).fill(1n);
8// Array to store modular multiplicative inverse of factorials: inverseFactorial[i] = (i!)^(-1) mod MODULO
9const inverseFactorial: bigint[] = Array(MAX_SIZE).fill(1n);
10
11/**
12 * Computes (base^exponent) % MODULO using fast exponentiation
13 * @param base - The base number
14 * @param exponent - The exponent value
15 * @returns The result of (base^exponent) % MODULO
16 */
17function quickPower(base: bigint, exponent: number): bigint {
18 let result = 1n;
19
20 while (exponent !== 0) {
21 // If current bit is 1, multiply result by base
22 if ((exponent & 1) === 1) {
23 result = (result * base) % MODULO;
24 }
25 // Square the base for next bit
26 base = (base * base) % MODULO;
27 // Right shift to process next bit
28 exponent >>= 1;
29 }
30
31 return result;
32}
33
34/**
35 * Initialize factorial and inverse factorial arrays
36 * Using Fermat's Little Theorem: a^(p-1) ≡ 1 (mod p) where p is prime
37 * Therefore: a^(-1) ≡ a^(p-2) (mod p)
38 */
39(function initializeFactorials() {
40 for (let i = 1; i < MAX_SIZE; ++i) {
41 // Calculate i! = (i-1)! * i
42 factorial[i] = (factorial[i - 1] * BigInt(i)) % MODULO;
43 // Calculate modular multiplicative inverse of i! using Fermat's Little Theorem
44 inverseFactorial[i] = quickPower(factorial[i], Number(MODULO - 2n));
45 }
46})();
47
48/**
49 * Calculates binomial coefficient C(totalItems, selectedItems) modulo MODULO
50 * Uses the formula: C(m, n) = m! / (n! * (m-n)!)
51 * @param totalItems - Total number of items (m)
52 * @param selectedItems - Number of items to select (n)
53 * @returns The binomial coefficient C(m, n) % MODULO
54 */
55function binomialCoefficient(totalItems: number, selectedItems: number): bigint {
56 // C(m, n) = m! * (n!)^(-1) * ((m-n)!)^(-1) mod MODULO
57 return (((factorial[totalItems] * inverseFactorial[selectedItems]) % MODULO) *
58 inverseFactorial[totalItems - selectedItems]) % MODULO;
59}
60
61/**
62 * Counts the number of good arrays with given parameters
63 * @param n - Length of the array
64 * @param m - Maximum value that can be used in the array
65 * @param k - Number of distinct adjacent pairs required
66 * @returns The count of good arrays modulo 10^9 + 7
67 */
68export function countGoodArrays(n: number, m: number, k: number): number {
69 // Formula: C(n-1, k) * m * (m-1)^(n-k-1)
70 // - C(n-1, k): Choose k positions out of n-1 adjacent pairs to be different
71 // - m: Number of choices for the first element
72 // - (m-1)^(n-k-1): For remaining positions, each has m-1 choices to maintain equality
73 const answer = (((binomialCoefficient(n - 1, k) * BigInt(m)) % MODULO) *
74 quickPower(BigInt(m - 1), n - k - 1)) % MODULO;
75
76 return Number(answer);
77}
78
Time and Space Complexity
The preprocessing phase computes factorial values f[i]
and their modular inverses g[i]
for all values up to mx
. This takes O(mx)
time for computing factorials and O(mx * log(mod))
time for computing modular inverses using pow(f[i], mod - 2, mod)
. The space complexity for preprocessing is O(mx)
to store arrays f
and g
.
For the main function countGoodArrays
:
- The
comb(n - 1, k)
function performsO(1)
operations since it just accesses precomputed values and does constant multiplications. - Computing
pow(m - 1, n - k - 1, mod)
takesO(log(n - k - 1))
time using fast exponentiation, which simplifies toO(log(n - k))
. - The remaining operations (multiplications and modulo) are
O(1)
.
Therefore, ignoring the preprocessing time and space:
- Time Complexity:
O(log(n - k))
due to the modular exponentiation operation - Space Complexity:
O(1)
as the function only uses a constant amount of additional space for storing intermediate results
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Intermediate Calculations
Pitfall: Even though we're using modular arithmetic, intermediate multiplication results can overflow before applying the modulo operation. For example, when calculating factorial[total] * factorial_inverse[choose] * factorial_inverse[total - choose]
, the intermediate product might exceed Python's integer limits in other languages or cause precision issues.
Solution: Apply modulo after each multiplication operation rather than at the end:
# Instead of: return factorial[total] * factorial_inverse[choose] * factorial_inverse[total - choose] % MOD # Use: result = factorial[total] * factorial_inverse[choose] % MOD result = result * factorial_inverse[total - choose] % MOD return result
2. Edge Case: k = 0
Pitfall: When k = 0
, no consecutive elements should be equal. The formula pow(m - 1, n - k - 1, MOD)
becomes pow(m - 1, n - 1, MOD)
. However, if not careful with the logic, one might forget that the first element can still be any of the m
values.
Solution: The current formula handles this correctly, but it's important to verify:
- For
k = 0
: Result should bem * (m-1)^(n-1)
(first element: m choices, rest: m-1 choices each) - Test with small examples:
n=3, m=2, k=0
should give2 * 1 * 1 = 2
arrays:[1,2,1]
and[2,1,2]
3. Edge Case: k = n - 1
Pitfall: When k = n - 1
, all consecutive pairs must be equal, meaning all elements must be the same. The formula pow(m - 1, n - k - 1, MOD)
becomes pow(m - 1, -1, MOD)
, which would cause an error or unexpected behavior.
Solution: Handle this special case explicitly:
def countGoodArrays(self, n: int, m: int, k: int) -> int:
if k == n - 1:
# All elements must be the same
return m
return combination(n - 1, k) * m * pow(m - 1, n - k - 1, MOD) % MOD
4. Invalid Input: k > n - 1
Pitfall: If k > n - 1
, it's impossible to have more equal consecutive pairs than there are consecutive pairs in total. The combination function would fail or return 0.
Solution: Add input validation:
def countGoodArrays(self, n: int, m: int, k: int) -> int:
if k > n - 1 or k < 0:
return 0
if k == n - 1:
return m
return combination(n - 1, k) * m * pow(m - 1, n - k - 1, MOD) % MOD
5. Precomputation Array Size
Pitfall: The precomputed factorial arrays have a fixed size (MAX_SIZE = 10**5 + 10
). If n
exceeds this limit, accessing factorial[n-1]
would cause an index error.
Solution: Either:
- Increase
MAX_SIZE
based on problem constraints - Add dynamic computation for values beyond the precomputed range
- Add bounds checking:
def countGoodArrays(self, n: int, m: int, k: int) -> int:
if n - 1 >= MAX_SIZE:
# Handle large n values with dynamic computation
# or raise an appropriate error
raise ValueError(f"n value {n} exceeds precomputed limits")
# ... rest of the solution
6. Modular Inverse Computation
Pitfall: Using pow(factorial[i], MOD - 2, MOD)
assumes MOD is prime (which it is for 10^9 + 7
). If the modulo value changes to a non-prime number, this method won't work.
Solution: Document the assumption or use the Extended Euclidean Algorithm for a more general solution:
def mod_inverse(a, mod):
"""Compute modular inverse using Extended Euclidean Algorithm"""
def extended_gcd(a, b):
if a == 0:
return b, 0, 1
gcd, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return gcd, x, y
_, x, _ = extended_gcd(a % mod, mod)
return (x % mod + mod) % mod
Which data structure is used to implement priority queue?
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!