Problem overview
The function countNumbers(n, p) 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 can't start with 0, so a 3-digit number runs from 100 to 999.
For n = 3 and p = 6 the answer is 9. The digits can be 1, 2 and 3 in any order, which gives 6 numbers (123, 132, 213, 231, 312, 321), or 1, 1 and 6 in any order, which gives 3 more (116, 161, 611). The notes say N <= 12, and the return type is Long because counts grow past what an Int can hold.
Approach: track the product so far, with one bucket for "too big"
Checking every number with n digits means up to 900 billion candidates for n = 12, which is far too slow. Instead, build numbers one digit at a time and count how many numbers share the same product. Let dp[i][j] be the number of i-digit numbers whose digit product is j. Appending a digit d to such a number gives an (i + 1)-digit number with product j * d, so row i + 1 can be computed from row i.
The product can grow huge (nine 9s give 387,420,489), but only products up to p matter, because digits can't bring a product back down except by a 0. The table therefore gets one extra column, p + 1, that stands for "the product is larger than p". It is needed rather than optional: a number with a 0 digit has product 0, and a product that is above p today still turns into 0 when a 0 digit arrives. When p is 0, every number starts above p and is rescued only by a 0 digit later.
A product above p stays above p when multiplied by 1 to 9, so those digits keep the number in the extra column, and a 0 digit sends it to column 0. That is why the same formula, "multiply and cap at p + 1", works for every column.
Step by step
- Handle
n == 0. A number has at least one digit, so there is nothing to count. Return 0 before building the table, which would otherwise write into a row 1 that doesn't exist. - Create the table as
n + 1rows ofLongArray(p + 2): columns 0 topfor exact products, and columnp + 1for "bigger thanp". - Fill row 1. Loop over the digits 1 to 9 (the first digit can't be 0). The digit
ialone has producti, so add 1 to columnminOf(i, p + 1). Use+=: whenpis small, several digits land in the extra column. - Extend row by row. Loop
ifrom 1 untiln. Each pass reads the finished rowiand fills rowi + 1. - Loop over every column
jfrom 0 top + 1, including the extra one and column 0. - Try every digit
dfrom 0 to 9. Zero is allowed here, because only the first digit is restricted. - Compute the new column.
colisminOf(j * d, p + 1). - Pass the count forward. Add
dp[i][j]todp[i + 1][col]. If no number has productj, the cell holds 0 and adding it changes nothing, so no check is needed. - Return
dp[n][p]. Forn = 1the loops don't run and row 1 is already the answer.
The solution in Kotlin
class Solution {
fun countNumbers(n: Int, p: Int): Long {
if (n == 0) {
return 0
}
val dp = Array(n + 1) { LongArray(p + 2) }
for (i in 1 until 10) {
dp[1][minOf(i, p + 1)] += 1
}
for (i in 1 until n) {
for (j in 0..p + 1) {
for (d in 0 until 10) {
val col = minOf(j * d, p + 1)
dp[i + 1][col] += dp[i][j]
}
}
}
return dp[n][p]
}
}
The Kotlin details worth a closer look:
Longfor counts. The result type isLong, and so are the table cells. Forn = 12andp = 0the answer is 617570463519, far beyond the 2,147,483,647 anIntholds. AnInttable would wrap around silently instead of reporting an error.- Returning a literal.
return 0in a function returningLongcompiles: Kotlin treats the integer literal as aLongbecause the expected type is known. += 1on aLongArrayelement. The literal 1 is also read as aLonghere.- Ranges.
1 until 10is the digits 1 to 9.0..p + 1includesp + 1, which is the extra column, because the range operator has lower precedence than+.0 until 10is the digits 0 to 9. j * dis anIntproduct. The column index is at most(p + 1) * 9, so it overflows only for apabove 200 million, where the table itself would no longer fit in memory.
Complexity
Time: O(n * p). There are n - 1 passes, each over p + 2 columns and 10 digits, so about 10 * n * (p + 2) steps.
Space: O(n * p). The table has (n + 1) * (p + 2) cells. Only two rows are alive at a time, so two rows would be enough if memory mattered.
Edge cases and common mistakes
- Two-digit numbers with a zero.
countNumbers(2, 0)returns 9: the numbers 10, 20, up to 90. Numbers like 01 don't count, since the first digit can't be 0. - All twelve digits, product zero.
countNumbers(12, 0)returns 617570463519, which is 900000000000 numbers minus the 282429536481 numbers with no 0 digit at all (nine choices for each of the twelve digits). - One digit.
countNumbers(1, 5)returns 1.countNumbers(1, 0)returns 0, since 0 is not a one-digit number here. - A product no number reaches.
countNumbers(2, 100)returns 0, because 100 can't be written as a product of two digits. - Dropping the extra column. Without it,
p = 0breaks: numbers like 5 followed by 0 would be lost along the way. - Starting digits at 0. Looping
0 until 10in row 1 would count numbers with a leading zero. - Using
Intcells. The answers for largenand smallpwrap around.
Testing
n = 3,p = 6gives9.n = 2,p = 7gives2(17 and 71).n = 3,p = 1gives1(111).n = 4,p = 1gives1(1111).n = 2,p = 0gives9.