Problem overview
The function count_numbers receives n and p and returns how many base-10 numbers with exactly n digits have a digit product equal to p. The digit product is what you get by multiplying all the digits together. A number cannot start with 0.
For n = 3 and p = 6 the answer is 9. The digits {1, 2, 3} give six numbers (123, 132, 213, 231, 312, 321), and the digits {1, 1, 6} give three (116, 161, 611).
Approach: count by product, one digit at a time
With n up to 12 there are up to 9 * 10^11 candidates, far too many to test one by one. The key fact is that appending a digit d to a number multiplies its product by d. So the product of a longer number depends only on the product of the shorter one and the new digit, and the shorter number's digits no longer matter.
Let dp[i][j] be the number of i-digit numbers with product j. Row 1 is simple: each digit 1 to 9 is a one-digit number with product equal to itself. Row i + 1 comes from row i by trying all ten appended digits.
There is one catch. Products can get big, and the table can only have columns up to p. Dropping a product larger than p is wrong when p is 0: the product of every number is above 0 until it meets a 0 digit, and then it becomes 0. So the table gets one extra column, p + 1, that stands for "any product larger than p". A product above p times a digit from 1 to 9 stays above p, and times 0 it becomes 0, so this single bucket tracks everything correctly.
Step by step
- Handle
n = 0. A number has at least one digit, so there is nothing to count. Return 0 early with thereturnkeyword and a semicolon. The table below writes to row 1, which exists only whennis at least 1. - Convert and build the table. Shadow
nandpasusize, then createn + 1rows ofp + 2zeros: columns 0 topplus the overflow column. The cells arei64because the counts outgrowi32: among 12-digit numbers, more than 600 billion contain a 0 digit. - Fill row 1. Loop over the digits 1 to 9 (the range
1..10, which excludes 10), since a number can't start with 0. - Place each digit. Add 1 to
dp[1][min(i, p + 1)]. The digitihas producti, unless that is abovep, in which case it goes to the overflow column. Use+=: whenpis small, several digits land in the same column. - Extend row by row. Loop
ifrom 1 ton - 1. The pass forifills rowi + 1. - Loop over every column
jfrom 0 top + 1, including the overflow column and column 0. - Try every digit
dfrom 0 to 9. Only the first digit has to be non-zero, and row 1 already took care of that. - Find the new column. It is
min(j * d, p + 1): the old product times the digit, capped at the overflow column. - Add the counts:
dp[i + 1][col] += dp[i][j]. An empty cell adds 0, so no check is needed. - Return
dp[n][p]as the last expression. Forn = 1the row loop doesn't run and row 1 is already the answer.
The solution in Rust
impl Solution {
fn count_numbers(n: i32, p: i32) -> i64 {
if n == 0 {
return 0;
}
let n = n as usize;
let p = p as usize;
let mut dp = vec![vec![0i64; p + 2]; n + 1];
for i in 1..10 {
dp[1][std::cmp::min(i, p + 1)] += 1;
}
for i in 1..n {
for j in 0..=p + 1 {
for d in 0..10usize {
let col = std::cmp::min(j * d, p + 1);
dp[i + 1][col] += dp[i][j];
}
}
}
dp[n][p]
}
}
The Rust details worth knowing:
- Early
returnversus tail expression. Leaving a function in the middle needsreturn 0;. The final linedp[n][p]has noreturnand no semicolon; with a semicolon it would become a statement and the function would yield(). - Integer types never mix. In the first loop,
iis passed tomintogether withp + 1, so the compiler makes it ausize. In the inner loopdis written0..10usizeso thatj * dmultiplies twousizevalues; a plain0..10would be inferred from the multiplication too, but the suffix states the type. - Why
i64. The result typei64holds up to about 9.2 * 10^18. Ani32tops out near 2.1 * 10^9. In a build with optimizations, as the lessons use, overflow wraps silently, so a too-small type gives wrong answers with no error. - The range
0..=p + 1. The operator+binds tighter than..=, so this means0..=(p + 1). - Cloned rows.
vec![vec![0i64; p + 2]; n + 1]makesn + 1independent rows, so writing to rowi + 1doesn't change rowi.
Complexity
Time: O(n * p * 10), that is O(n * p). For each of the n - 1 row transitions there are p + 2 columns and 10 digits.
Space: O(n * p). The table has (n + 1) * (p + 2) cells. A very large p would need a very large table, so the method suits moderate values of p.
Edge cases and common mistakes
- Zero digits.
n = 0returns 0 through the early return. - Product 0.
n = 2,p = 0gives 9: the numbers 10, 20, ..., 90. The overflow column is what makes this work, since every starting digit begins above 0. - One digit.
n = 1,p = 5gives 1. The lesson's table counts only the digits 1 to 9 for one-digit numbers, son = 1,p = 0gives 0. - Impossible products.
n = 2,p = 100gives 0, since no two digits multiply to 100. - Dropping large products. Skipping every product above
pinstead of using the overflow column makesp = 0answers wrong. - Allowing a leading 0. Starting row 1 from digit 0 would count numbers such as 05 as two-digit numbers.
- Using
i32cells. Forn = 12,p = 0the answer, 617570463519, is far beyondi32.
Testing
n = 3,p = 6gives9.n = 2,p = 12gives4: 26, 62, 34 and 43.n = 3,p = 7gives3: 117, 171 and 711.n = 2,p = 0gives9.n = 12,p = 0gives617570463519.
