Facebook Pixel

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) where arr[1] == arr[0]
  • The array [1,2,2,2] would have k=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?

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 to m, giving us m 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 have n - 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] stores i! modulo 10^9 + 7
  • g[i] stores the modular inverse of f[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:

  1. comb(n - 1, k): Number of ways to choose which k positions have equal consecutive elements
  2. m: Number of choices for the first segment's value
  3. pow(m - 1, n - k - 1, mod): Number of ways to assign values to the remaining n - k - 1 segments, where each must differ from the previous
  4. 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) where mx = 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 Evaluator

Example 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 performs O(1) operations since it just accesses precomputed values and does constant multiplications.
  • Computing pow(m - 1, n - k - 1, mod) takes O(log(n - k - 1)) time using fast exponentiation, which simplifies to O(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 be m * (m-1)^(n-1) (first element: m choices, rest: m-1 choices each)
  • Test with small examples: n=3, m=2, k=0 should give 2 * 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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More