2338. Count the Number of Ideal Arrays
Problem Description
You need to count how many distinct "ideal arrays" of length n
can be formed, where each element is between 1 and maxValue
.
An array is considered "ideal" if it satisfies two conditions:
- Every element must be a value from 1 to
maxValue
(inclusive) - Each element (except the first) must be divisible by the element immediately before it - meaning
arr[i]
is divisible byarr[i-1]
for all positionsi > 0
For example, [1, 2, 6]
is an ideal array because 2 is divisible by 1, and 6 is divisible by 2. Similarly, [3, 6, 6]
is ideal because 6 is divisible by 3, and 6 is divisible by 6.
The key insight is that in an ideal array, the values form a non-decreasing sequence where each element is a multiple of the previous one. This means if we start with value a
, the next value must be a*k
for some integer k ≥ 1
.
The solution uses dynamic programming where f[i][j]
represents the number of distinct sequences ending with value i
and having exactly j
distinct values. Since we have an array of length n
but only j
distinct values, we need to distribute these j
values across n
positions. This is done using combinations - specifically C(n-1, j-1)
, which represents choosing j-1
positions from n-1
possible positions to place the transitions between different values.
The algorithm:
- Preprocesses combination values using Pascal's triangle
- Computes
f[i][j]
by considering all multiples of each value - Combines the results by multiplying the number of distinct sequences with the ways to distribute them across the array length
Since the answer can be very large, return it modulo 10^9 + 7
.
Intuition
The key observation is that an ideal array has a special structure: each element must be a multiple of the previous one. This means if we look at the sequence of distinct values in the array, they form a divisibility chain like [a, a*k₁, a*k₁*k₂, ...]
.
Let's think about this problem in two parts:
-
What distinct values can appear in the array? Since each value must divide the next, we're essentially building chains of divisors. For example, starting from 2, we could have chains like
[2, 4, 8]
or[2, 6, 12]
. -
How can we arrange these distinct values in an array of length
n
? Since the array must be non-decreasing (due to the divisibility constraint), we can have repetitions. For instance, with distinct values[2, 4]
and array length 4, we could have[2, 2, 4, 4]
or[2, 4, 4, 4]
.
This leads us to think about the problem as:
- First, find all possible chains of distinct values where each divides the next
- Then, figure out how to distribute these values across
n
positions
For the first part, we can use dynamic programming. Let f[i][j]
represent the number of ways to form a chain of exactly j
distinct values ending with value i
. We can build longer chains by considering all multiples of i
: if we have a chain ending at i
with j
values, we can extend it to any multiple k*i
(where k*i ≤ maxValue
) to get a chain with j+1
values.
For the second part, imagine we have j
distinct values to place in n
positions. Since the array must be non-decreasing, this is equivalent to dividing n
positions into j
groups (where each group contains copies of the same value). This is a classic "stars and bars" problem - we need to place j-1
dividers among n-1
possible positions, giving us C(n-1, j-1)
ways.
The maximum number of distinct values in any chain is limited because each value must be at least double the previous one (smallest multiplier is 2). So the longest possible chain has length approximately log₂(maxValue)
, which is why we only need to consider up to about 15 distinct values.
By combining these two insights - counting the chains of distinct values and then distributing them across the array - we arrive at the final answer: sum over all possible ending values i
and chain lengths j
of f[i][j] * C(n-1, j-1)
.
Learn more about Math, Dynamic Programming and Combinatorics patterns.
Solution Approach
The implementation consists of three main parts: preprocessing combinations, computing the DP table for distinct value chains, and calculating the final answer.
Step 1: Precompute Combinations using Pascal's Triangle
We build a 2D array c[i][j]
to store C(i, j)
values using the recurrence relation:
c[i][j] = c[i-1][j] + c[i-1][j-1]
- Base case:
c[i][0] = 1
for alli
c = [[0] * 16 for _ in range(n)]
for i in range(n):
for j in range(min(16, i + 1)):
c[i][j] = 1 if j == 0 else (c[i - 1][j] + c[i - 1][j - 1]) % mod
We only need combinations up to 16 columns because the maximum chain length is bounded by log₂(maxValue) + 1 ≈ 15
.
Step 2: Build DP Table for Distinct Value Chains
Initialize f[i][j]
where:
i
represents the ending value (1 tomaxValue
)j
represents the number of distinct values in the chainf[i][j]
= number of ways to form a chain ofj
distinct values ending ati
Base case: f[i][1] = 1
for all i
(single element chains).
f = [[0] * 16 for _ in range(maxValue + 1)]
for i in range(1, maxValue + 1):
f[i][1] = 1
For each chain length j
from 1 to 14, and each value i
, we extend the chain to all its multiples:
for j in range(1, 15):
for i in range(1, maxValue + 1):
k = 2
while k * i <= maxValue:
f[k * i][j + 1] = (f[k * i][j + 1] + f[i][j]) % mod
k += 1
This builds chains by considering: if we have a chain of length j
ending at i
, we can extend it to k*i
(for k ≥ 2
) to create a chain of length j+1
.
Step 3: Calculate Final Answer
For each possible ending value i
and chain length j
, multiply:
f[i][j]
: number of distinct value chainsc[n-1][j-1]
: ways to distributej
distinct values acrossn
positions
ans = 0
for i in range(1, maxValue + 1):
for j in range(1, 16):
ans = (ans + f[i][j] * c[-1][j - 1]) % mod
The formula c[n-1][j-1]
represents choosing j-1
positions from n-1
possible spots to place dividers, effectively distributing j
distinct values into n
array positions while maintaining non-decreasing order.
Time Complexity: O(maxValue × log² maxValue)
- For each value up to maxValue
, we consider chains up to length log maxValue
, and for each chain, we check multiples up to maxValue
.
Space Complexity: O(n × log maxValue + maxValue × log maxValue)
- For storing the combination table and DP table.
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 small example with n = 3
and maxValue = 5
to illustrate the solution approach.
Step 1: Precompute Combinations
We need combinations C(i,j)
for distributing distinct values. For n=3
, we build:
c[0][0] = 1 c[1][0] = 1, c[1][1] = 1 c[2][0] = 1, c[2][1] = 2, c[2][2] = 1
So C(2,0) = 1
, C(2,1) = 2
, C(2,2) = 1
.
Step 2: Build DP Table for Distinct Value Chains
Initialize f[i][1] = 1
for all values 1 to 5 (single-element chains):
f[1][1] = 1
,f[2][1] = 1
,f[3][1] = 1
,f[4][1] = 1
,f[5][1] = 1
Now build chains of length 2 by considering multiples:
- From value 1: multiples are 2,3,4,5 →
f[2][2] = 1
,f[3][2] = 1
,f[4][2] = 1
,f[5][2] = 1
- From value 2: multiple is 4 →
f[4][2] += 1
(nowf[4][2] = 2
)
After processing all values for length 2:
f[2][2] = 1
(chain: 1→2)f[3][2] = 1
(chain: 1→3)f[4][2] = 2
(chains: 1→4 and 2→4)f[5][2] = 1
(chain: 1→5)
Build chains of length 3:
- From
f[2][2] = 1
: multiple is 4 →f[4][3] = 1
(chain: 1→2→4)
Step 3: Calculate Final Answer
Now we combine the chains with ways to distribute them:
For chains of length 1 (need to fill 3 positions with 1 distinct value):
- Ways to distribute:
C(2,0) = 1
- Contribution:
(f[1][1] + f[2][1] + f[3][1] + f[4][1] + f[5][1]) × 1 = 5 × 1 = 5
- Examples: [1,1,1], [2,2,2], [3,3,3], [4,4,4], [5,5,5]
For chains of length 2 (need to fill 3 positions with 2 distinct values):
- Ways to distribute:
C(2,1) = 2
(either [a,a,b] or [a,b,b]) - Contribution:
(f[2][2] + f[3][2] + f[4][2] + f[5][2]) × 2 = (1+1+2+1) × 2 = 10
- Examples for chain 1→2: [1,1,2] and [1,2,2]
- Examples for chain 2→4: [2,2,4] and [2,4,4]
For chains of length 3 (need to fill 3 positions with 3 distinct values):
- Ways to distribute:
C(2,2) = 1
(only [a,b,c]) - Contribution:
f[4][3] × 1 = 1 × 1 = 1
- Example: [1,2,4]
Total: 5 + 10 + 1 = 16 distinct ideal arrays
Some of the 16 arrays are:
- Length-1 chains: [1,1,1], [2,2,2], [3,3,3], [4,4,4], [5,5,5]
- Length-2 chains: [1,1,2], [1,2,2], [1,1,3], [1,3,3], [1,1,4], [1,4,4], [2,2,4], [2,4,4], [1,1,5], [1,5,5]
- Length-3 chain: [1,2,4]
Solution Implementation
1class Solution:
2 def idealArrays(self, n: int, maxValue: int) -> int:
3 MOD = 10**9 + 7
4 MAX_LENGTH = 16 # Maximum possible length of strictly increasing subsequence
5
6 # Build Pascal's triangle for combination calculations
7 # combinations[i][j] represents C(i, j) = number of ways to choose j items from i items
8 combinations = [[0] * MAX_LENGTH for _ in range(n)]
9
10 for i in range(n):
11 for j in range(min(MAX_LENGTH, i + 1)):
12 if j == 0:
13 combinations[i][j] = 1 # C(i, 0) = 1
14 else:
15 # Pascal's triangle formula: C(i, j) = C(i-1, j) + C(i-1, j-1)
16 combinations[i][j] = (combinations[i - 1][j] + combinations[i - 1][j - 1]) % MOD
17
18 # dp[value][length] = number of strictly increasing sequences ending at 'value' with 'length' elements
19 dp = [[0] * MAX_LENGTH for _ in range(maxValue + 1)]
20
21 # Base case: all values can form a sequence of length 1
22 for value in range(1, maxValue + 1):
23 dp[value][1] = 1
24
25 # Build up sequences of increasing lengths
26 for length in range(1, MAX_LENGTH - 1):
27 for current_value in range(1, maxValue + 1):
28 # Try all multiples of current_value as next element in sequence
29 multiplier = 2
30 while multiplier * current_value <= maxValue:
31 next_value = multiplier * current_value
32 # Extend sequences ending at current_value by adding next_value
33 dp[next_value][length + 1] = (dp[next_value][length + 1] + dp[current_value][length]) % MOD
34 multiplier += 1
35
36 # Calculate final answer by summing over all possible ending values and sequence lengths
37 answer = 0
38 for ending_value in range(1, maxValue + 1):
39 for sequence_length in range(1, MAX_LENGTH):
40 # For each strictly increasing sequence, we can arrange it in the array of size n
41 # using combinations to place the increasing elements
42 ways_to_arrange = combinations[n - 1][sequence_length - 1]
43 answer = (answer + dp[ending_value][sequence_length] * ways_to_arrange) % MOD
44
45 return answer
46
1class Solution {
2 public int idealArrays(int n, int maxValue) {
3 final int MOD = (int) 1e9 + 7;
4
5 // Pascal's triangle for calculating combinations C(n, k)
6 // combinations[i][j] represents C(i, j) = number of ways to choose j items from i items
7 int[][] combinations = new int[n][16];
8 for (int i = 0; i < n; i++) {
9 for (int j = 0; j <= i && j < 16; j++) {
10 if (j == 0) {
11 combinations[i][j] = 1; // C(i, 0) = 1
12 } else {
13 // Pascal's triangle formula: C(i, j) = C(i-1, j) + C(i-1, j-1)
14 combinations[i][j] = (combinations[i - 1][j] + combinations[i - 1][j - 1]) % MOD;
15 }
16 }
17 }
18
19 // Dynamic programming table for counting distinct sequences
20 // dp[value][length] = number of strictly increasing sequences ending with 'value' of length 'length'
21 long[][] dp = new long[maxValue + 1][16];
22
23 // Base case: all values can form a sequence of length 1
24 for (int value = 1; value <= maxValue; value++) {
25 dp[value][1] = 1;
26 }
27
28 // Build up sequences of increasing length
29 for (int length = 1; length < 15; length++) {
30 for (int currentValue = 1; currentValue <= maxValue; currentValue++) {
31 // For each multiple of currentValue, we can extend the sequence
32 for (int multiplier = 2; multiplier * currentValue <= maxValue; multiplier++) {
33 int nextValue = multiplier * currentValue;
34 // Extend sequences ending at currentValue with length 'length'
35 // to sequences ending at nextValue with length 'length + 1'
36 dp[nextValue][length + 1] = (dp[nextValue][length + 1] + dp[currentValue][length]) % MOD;
37 }
38 }
39 }
40
41 // Calculate the final answer by combining sequences with array positions
42 long answer = 0;
43 for (int endValue = 1; endValue <= maxValue; endValue++) {
44 for (int sequenceLength = 1; sequenceLength < 16; sequenceLength++) {
45 // For each distinct increasing sequence of length 'sequenceLength' ending with 'endValue',
46 // we can distribute it across n positions in C(n-1, sequenceLength-1) ways
47 answer = (answer + dp[endValue][sequenceLength] * combinations[n - 1][sequenceLength - 1]) % MOD;
48 }
49 }
50
51 return (int) answer;
52 }
53}
54
1class Solution {
2public:
3 int idealArrays(int n, int maxValue) {
4 const int MOD = 1e9 + 7;
5
6 // Step 1: Precompute binomial coefficients C(n-1, k) for k from 0 to 14
7 // binomialCoeff[i][j] represents C(i, j) = number of ways to choose j items from i items
8 vector<vector<int>> binomialCoeff(n, vector<int>(16));
9 for (int i = 0; i < n; ++i) {
10 for (int j = 0; j <= i && j < 16; ++j) {
11 if (j == 0) {
12 // Base case: C(i, 0) = 1 (one way to choose nothing)
13 binomialCoeff[i][j] = 1;
14 } else {
15 // Pascal's triangle formula: C(i, j) = C(i-1, j) + C(i-1, j-1)
16 binomialCoeff[i][j] = (binomialCoeff[i - 1][j] + binomialCoeff[i - 1][j - 1]) % MOD;
17 }
18 }
19 }
20
21 // Step 2: Dynamic programming to count strictly increasing sequences
22 // dp[endValue][length] = number of strictly increasing sequences of given length ending with endValue
23 vector<vector<long long>> dp(maxValue + 1, vector<long long>(16));
24
25 // Base case: all sequences of length 1 ending with any value have exactly 1 way
26 for (int value = 1; value <= maxValue; ++value) {
27 dp[value][1] = 1;
28 }
29
30 // Build up sequences of increasing length
31 for (int length = 1; length < 15; ++length) {
32 for (int currentValue = 1; currentValue <= maxValue; ++currentValue) {
33 // For each multiple of currentValue, extend the sequence
34 for (int multiplier = 2; multiplier * currentValue <= maxValue; ++multiplier) {
35 int nextValue = multiplier * currentValue;
36 // A sequence of length 'length' ending at currentValue can be extended
37 // to a sequence of length 'length+1' ending at nextValue
38 dp[nextValue][length + 1] = (dp[nextValue][length + 1] + dp[currentValue][length]) % MOD;
39 }
40 }
41 }
42
43 // Step 3: Calculate the final answer
44 // For each possible ending value and sequence length, multiply by the number of ways
45 // to distribute the sequence across n positions (stars and bars method)
46 long long totalCount = 0;
47 for (int endValue = 1; endValue <= maxValue; ++endValue) {
48 for (int seqLength = 1; seqLength < 16; ++seqLength) {
49 // dp[endValue][seqLength] gives strictly increasing sequences
50 // binomialCoeff[n-1][seqLength-1] gives ways to place these values in n positions
51 totalCount = (totalCount + dp[endValue][seqLength] * binomialCoeff[n - 1][seqLength - 1]) % MOD;
52 }
53 }
54
55 return totalCount;
56 }
57};
58
1/**
2 * Counts the number of ideal arrays of length n with maximum value maxValue
3 * An ideal array is a non-decreasing array where each element divides the next element
4 *
5 * @param n - The length of the array
6 * @param maxValue - The maximum value allowed in the array
7 * @returns The count of ideal arrays modulo 10^9 + 7
8 */
9function idealArrays(n: number, maxValue: number): number {
10 const MOD: number = 1e9 + 7;
11
12 // Build Pascal's triangle for combination calculations
13 // combinations[i][j] represents C(i, j) = i choose j
14 const combinations: number[][] = Array.from(
15 { length: n },
16 () => Array(16).fill(0)
17 );
18
19 // Calculate combinations using Pascal's triangle formula
20 for (let i = 0; i < n; i++) {
21 for (let j = 0; j <= i && j < 16; j++) {
22 if (j === 0) {
23 // Base case: C(i, 0) = 1
24 combinations[i][j] = 1;
25 } else {
26 // C(i, j) = C(i-1, j) + C(i-1, j-1)
27 combinations[i][j] = (combinations[i - 1][j] + combinations[i - 1][j - 1]) % MOD;
28 }
29 }
30 }
31
32 // Dynamic programming table for counting distinct sequences
33 // dp[endValue][length] = number of strictly increasing sequences ending at endValue with given length
34 const dp: number[][] = Array.from(
35 { length: maxValue + 1 },
36 () => Array(16).fill(0)
37 );
38
39 // Initialize base case: sequences of length 1
40 for (let value = 1; value <= maxValue; value++) {
41 dp[value][1] = 1;
42 }
43
44 // Build up sequences of increasing length
45 for (let length = 1; length < 15; length++) {
46 for (let currentValue = 1; currentValue <= maxValue; currentValue++) {
47 // Try all multiples of currentValue as the next element
48 for (let multiplier = 2; multiplier * currentValue <= maxValue; multiplier++) {
49 const nextValue: number = multiplier * currentValue;
50 // Add ways to form sequences of length+1 ending at nextValue
51 dp[nextValue][length + 1] = (dp[nextValue][length + 1] + dp[currentValue][length]) % MOD;
52 }
53 }
54 }
55
56 // Calculate total number of ideal arrays
57 let totalCount: number = 0;
58
59 // For each possible ending value and sequence length
60 for (let endValue = 1; endValue <= maxValue; endValue++) {
61 for (let sequenceLength = 1; sequenceLength < 16; sequenceLength++) {
62 // Multiply by ways to distribute n elements into sequenceLength groups
63 // This uses stars and bars method: C(n-1, sequenceLength-1)
64 const contribution: number = (dp[endValue][sequenceLength] * combinations[n - 1][sequenceLength - 1]) % MOD;
65 totalCount = (totalCount + contribution) % MOD;
66 }
67 }
68
69 return totalCount;
70}
71
Time and Space Complexity
Time Complexity: O(n × 16 + maxValue × 16 × maxValue + maxValue × 16)
which simplifies to O(n + maxValue²)
The time complexity breaks down into three main parts:
-
Building the combination table
c
: The nested loops run forn
iterations and up to16
iterations, givingO(n × 16) = O(n)
. -
Computing the dynamic programming table
f
:- The outer loop runs for
15
iterations (j from 1 to 14) - The middle loop runs for
maxValue
iterations (i from 1 to maxValue) - The inner while loop runs approximately
maxValue/i
times for each i - The total iterations for the inner loop across all values of i is approximately
maxValue × (1/1 + 1/2 + 1/3 + ... + 1/maxValue)
≈maxValue × log(maxValue)
- However, since we're iterating through all multiples, the total work is
O(15 × maxValue × maxValue) = O(maxValue²)
- The outer loop runs for
-
Computing the final answer: Two nested loops running
maxValue × 16
times, givingO(maxValue × 16) = O(maxValue)
.
Overall: O(n + maxValue² + maxValue) = O(n + maxValue²)
Space Complexity: O(n × 16 + maxValue × 16)
which simplifies to O(n + maxValue)
The space complexity comes from:
- Array
c
of sizen × 16
=O(n)
- Array
f
of size(maxValue + 1) × 16
=O(maxValue)
Overall: O(n + maxValue)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Understanding of Array Distribution
Pitfall: A common mistake is misunderstanding how to distribute j
distinct values across n
positions. Many incorrectly assume we need C(n, j)
combinations, which would be choosing j
positions out of n
for the distinct values.
Why it's wrong: The array must be non-decreasing, and we're not choosing which positions get which values. Instead, we're determining where transitions between different values occur.
Correct approach: Use C(n-1, j-1)
which represents choosing j-1
transition points from n-1
possible positions between elements. This divides the array into j
segments, each containing copies of the same value.
Example: For array length n=5
with j=3
distinct values [a, b, c]
:
- We need 2 transition points (from
a
tob
, and fromb
toc
) - These 2 points can be chosen from 4 possible positions (between elements)
- Result:
C(4, 2) = 6
ways to arrange like[a,a,b,b,c]
,[a,b,b,b,c]
, etc.
2. Off-by-One Errors in DP Transition
Pitfall: Starting the multiplier k
from 1 instead of 2 when extending chains:
# Wrong k = 1 # This would include the same value, not creating a strictly increasing sequence while k * i <= maxValue: f[k * i][j + 1] = (f[k * i][j + 1] + f[i][j]) % mod k += 1
Why it's wrong: Starting from k=1
would mean f[i][j+1]
includes transitions from f[i][j]
, creating sequences with repeated consecutive values that aren't strictly increasing in terms of distinct values.
Correct approach: Start k
from 2 to ensure each new value is strictly greater than the previous:
k = 2 # Ensures k*i > i, maintaining strictly increasing distinct values while k * i <= maxValue: f[k * i][j + 1] = (f[k * i][j + 1] + f[i][j]) % mod k += 1
3. Insufficient Array Bounds for Chain Length
Pitfall: Using a fixed small size like 10 or 12 for the maximum chain length without proper calculation:
# Potentially wrong MAX_LENGTH = 10 # Too small for some inputs
Why it's wrong: The maximum chain length depends on maxValue
. For example, the chain [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
has 11 distinct values when maxValue ≥ 1024
.
Correct approach: Calculate based on the logarithm of maxValue
. Since each value must be at least double the previous one in the worst case, the maximum chain length is approximately log₂(maxValue) + 1
:
MAX_LENGTH = 16 # Safe for maxValue up to 10^4 # Or dynamically: MAX_LENGTH = min(16, int(math.log2(maxValue)) + 2)
4. Forgetting Modulo Operations
Pitfall: Not applying modulo consistently, especially in the combination calculation:
# Wrong - can cause integer overflow combinations[i][j] = combinations[i - 1][j] + combinations[i - 1][j - 1]
Correct approach: Apply modulo after every arithmetic operation:
combinations[i][j] = (combinations[i - 1][j] + combinations[i - 1][j - 1]) % MOD
5. Mishandling the Final Answer Calculation
Pitfall: Using wrong indices for the combination lookup:
# Wrong ans = (ans + f[i][j] * c[n][j]) % mod # Wrong indices
Correct approach: Use c[n-1][j-1]
to properly distribute j
distinct values across n
positions:
ans = (ans + f[i][j] * c[n-1][j-1]) % mod
In a binary min heap, the minimum element can be found in:
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
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Don’t Miss This!