Problem overview
The method countNumbers(n, p) returns how many base-10 numbers with exactly n digits have a digit product equal to p. The digit product is the result of multiplying all the digits together. A number can't start with 0, and the notes limit n to 12.
For n = 3 and p = 6 the answer is 9. The digits must multiply to 6, which happens for the digit sets {1, 2, 3} (six orders: 123, 132, 213, 231, 312, 321) and {1, 1, 6} (three orders: 116, 161, 611).
The method returns a Long. Among 12-digit numbers, 617,570,463,519 contain a zero digit, and their product is 0, so for p = 0 the answer is far beyond the range of Int.
Approach: grow the number one digit at a time
There are 9 * 10^11 numbers with 12 digits, too many to test one by one. Instead, count how many numbers of each length reach each product, and extend them digit by digit.
Let dp(i)(j) be the number of i-digit numbers whose digit product is j. Appending a digit d to such a number multiplies its product by d, so the count flows from dp(i)(j) into dp(i + 1)(j * d).
Products can grow past p, and the table can't have a column for every possible product. So the table has one extra column, p + 1, that stands for "greater than p". A product above p stays above p when it is multiplied by a digit from 1 to 9, and becomes 0 when it is multiplied by 0. That is the reason for the extra column rather than just discarding such numbers: when p is 0, a number whose product is already huge still ends up at 0 as soon as a zero digit is appended.
Take n = 2 and p = 7. Row 1 has a 1 in each of the columns 1 to 7, and a 2 in column 8 (the "above 7" column), because the digits 8 and 9 are larger than 7. In row 2, column 7 receives the count from column 1 with digit 7, and from column 7 with digit 1. Those are the numbers 17 and 71, so the answer is 2.
Step by step
- Handle
n = 0. A number has at least one digit, so return 0. This also protects the table, whose row 1 only exists whennis at least 1. - Create the table as an
Array.ofDim[Long]withn + 1rows andp + 2columns. The columns are products 0 topplus the "abovep" column. The cells areLongbecause the counts outgrowInt. - Loop over the first digit with
1 until 10. The digits are 1 to 9, because a number can't start with 0. - Fill row 1: add 1 to
dp(1)(math.min(i, p + 1)). A single digit is its own product. Digits larger thanpall land in columnp + 1, which is why the update uses+=and not=. - Loop over the lengths with
1 until n. Passiextends rowiinto rowi + 1. - Loop over the products with
0 to p + 1, including column 0 (numbers with a zero digit) and the "abovep" column. - Try every appended digit
dfrom 0 to 9. Zero is allowed here, since only the first digit is restricted. - Find the destination column:
col = math.min(j * d, p + 1). - Move the count: add
dp(i)(j)todp(i + 1)(col). If no number has productj, the cell holds 0 and the addition changes nothing. - Return
dp(n)(p). Forn = 1the loop over lengths doesn't run, and row 1 already holds the answer.
The solution in Scala
class Solution {
def countNumbers(n: Int, p: Int): Long = {
if (n == 0) {
return 0
}
val dp = Array.ofDim[Long](n + 1, p + 2)
for (i <- 1 until 10) {
dp(1)(math.min(i, p + 1)) += 1
}
for (i <- 1 until n) {
for (j <- 0 to p + 1) {
for (d <- 0 until 10) {
val col = math.min(j * d, p + 1)
dp(i + 1)(col) += dp(i)(j)
}
}
}
return dp(n)(p)
}
}
Some Scala details in this code:
LongagainstInt. AnIntholds values up to about 2.1 billion and wraps silently past that. The 12-digit count of 617,570,463,519 needs aLong, which is 64 bits. The method's return type, the table's element type and theArray.ofDim[Long]call all have to agree.return 0in aLongmethod. The literal0is anInt, and Scala widens it toLongautomatically, so this compiles.+= 1and+= dp(i)(j)on array cells update the cell in place. Adding anIntliteral to aLongcell is fine.- Early
return. Scala usually leaves the result as the last expression, but an early exit before the table is built is one of the places wherereturnis natural. - Nested loops over ranges.
1 until 10gives 1 to 9,0 until 10gives 0 to 9, and0 to p + 1includesp + 1. Eachforintroduces a fresh variable, so the two uses ofiin the two loops don't clash: the firstiis out of scope by the time the second begins.
Complexity
Time: O(n * p). For each of the n - 1 extension rounds, the loops visit p + 2 products and 10 digits. That is a constant 10 times n * p operations.
Space: O(n * p). The table has (n + 1) * (p + 2) cells. Each row depends only on the previous one, so two rows would be enough.
Edge cases and common mistakes
p = 0. Every number with a zero digit counts.countNumbers(2, 0)returns 9: the numbers 10, 20, ..., 90. With one digit the answer is 0, since a leading zero isn't allowed andcountNumbers(1, 0)returns 0.- Dropping the "above
p" column. Forp = 0, the table would lose every number like 51 whose product is positive until a later 0 appears. The answers for 3 or more digits would be too small. - Using
=in row 1. Withpbelow 9, several digits map to columnp + 1. Plain assignment keeps only the last one. - Appending only 1 to 9. The appended digit may be 0. Only the first digit is restricted, and without a zero option the case
p = 0returns 0 for every length. n = 1.countNumbers(1, 5)returns 1 (the digit 5), and a digit product larger than 9, such ascountNumbers(1, 12), returns 0.- Large products. A product that no digits can reach gives 0. For example
countNumbers(2, 12)is 4 (26, 34, 43, 62), while a prime like 11 is impossible and gives 0. - Index overflow in
j * d.jis at mostp + 1anddat most 9, so the product is safe for anypthat fits in memory as a table.
Testing
n = 3,p = 6gives9.n = 2,p = 7gives2.n = 2,p = 0gives9.n = 1,p = 5gives1.n = 0,p = 5gives0.n = 12,p = 0gives617570463519.
