Problem overview
The method CountNumbers(int n, int p) returns how many base-10 numbers have exactly n digits and a digit product equal to p. The digit product is the result of multiplying all the digits together. A number cannot start with 0, so a 3-digit number runs from 100 to 999.
For n = 3 and p = 6 the answer is 9. Three digits multiply to 6 when they are some arrangement of 1, 2, 3 (six numbers: 123, 132, 213, 231, 312, 321) or of 1, 1, 6 (three numbers: 116, 161, 611).
The result is a long, because the counts get large quickly. With n = 12 there are 900 billion 12-digit numbers, and more than 600 billion of them contain a 0 digit, so the count for p = 0 does not fit in an int.
Approach: extend numbers one digit at a time
Trying every n-digit number would mean up to 900 billion checks. Instead, count numbers by their current digit product and grow them digit by digit. Appending digit d to a number with product j gives a longer number with product j * d. So the counts for length i + 1 depend only on the counts for length i.
That suggests a table where dp[i, j] is the number of i-digit numbers with digit product j. There is one wrinkle: products can grow far beyond p. A number whose product has already exceeded p can still end up at p only if a later 0 digit sends the product to 0, and that matters when p is 0. Because products only grow when multiplied by digits from 1 to 9, it is enough to track the products from 0 to p exactly and to lump everything larger than p into one extra column, p + 1.
This works since a product above p can only stay above p (times 1 to 9) or drop to 0 (times 0). It can never come back down to a value between 1 and p, so which exact large value it had never matters.
Step by step
- Handle
n = 0. A number has at least one digit, so nothing qualifies and the method returns 0. This also keeps row 1 from being missing: the table needs row 1 to exist. - Create the table. A
long[,]withn + 1rows andp + 2columns: columns 0 topfor exact products and columnp + 1for "larger thanp". C# zero-fills it. - Seed the 1-digit numbers. Loop
iover the digits 1 to 9. A first digit cannot be 0. - Place each digit. The digit
ihas producti, so add 1 todp[1, Math.Min(i, p + 1)]. A digit abovepgoes into the overflow column. It uses+=because several digits can land in that same column whenpis small. - Loop over lengths
ifrom 1 ton - 1. Each pass reads rowiand writes rowi + 1. - Loop over every column
jfrom 0 top + 1, including column 0 (numbers containing a 0 digit) and the overflow column. - Try every next digit
dfrom 0 to 9. Zero is allowed here, because only the first digit must be non-zero. - Find the new column.
col = Math.Min(j * d, p + 1). For the overflow column this is still right:(p + 1) * dis at leastp + 1ford >= 1, and 0 ford = 0. - Add the counts.
dp[i + 1, col] += dp[i, j]. An empty cell contributes 0, so no check is needed. - Return
dp[n, p]. Forn = 1the loops do not run and row 1 already has the answer.
The solution in C#
class Solution {
public long CountNumbers(int n, int p) {
if (n == 0) {
return 0;
}
long[,] dp = new long[n + 1, p + 2];
for (int i = 1; i < 10; i++) {
dp[1, Math.Min(i, p + 1)] += 1;
}
for (int i = 1; i < n; i++) {
for (int j = 0; j <= p + 1; j++) {
for (int d = 0; d < 10; ++d) {
int col = Math.Min(j * d, p + 1);
dp[i + 1, col] += dp[i, j];
}
}
}
return dp[n, p];
}
}
Details worth knowing:
longcounts,intindex. The cells arelong, butcolis anintbecause it is used as an array index. The productj * dis computed inint, which is fine whilepis small enough for the table to fit in memory.- Rectangular array.
long[,]is a single block indexed asdp[i, j]. A jaggedlong[][]would need each row allocated separately. += 1versus= 1. Forp = 5, the digits 6, 7, 8 and 9 all map to column 6. Plain assignment would keep only one of them.- The clamp keeps indexes valid. Without
Math.Min,j * dcould be as large as9 * (p + 1), past the last column.
Complexity
Time: O(n * p * 10), which is O(n * p). There are n - 1 passes, p + 2 columns, and 10 digits for each.
Space: O(n * p). The table has (n + 1) * (p + 2) long cells. Only two rows are ever read and written together, so two rows would suffice.
Edge cases and common mistakes
p = 0. Every number is "larger than 0" until it gets its first 0 digit. Dropping the overflow column would make the answer wrong, because those numbers must still be extended. Forn = 2the answer is 9: 10, 20, ..., 90.n = 1.CountNumbers(1, 5)is 1, andCountNumbers(1, 0)is 0, because a 1-digit number cannot be 0 here: the first digit runs from 1 to 9.- A product no digits can make. With
p = 11, which has a prime factor above 9, no number qualifies, and the table simply holds 0. - Using
intfor the table. The count forn = 12andp = 0is 617,570,463,519, well aboveint.MaxValue. In anintcell it would wrap around silently. - Allowing a leading 0. Seeding row 1 with digit 0 would count numbers such as 05 as 2-digit numbers.
- Skipping column 0 in the loop. Numbers with a 0 digit keep growing, and each new digit keeps their product at 0.
Testing
n = 3,p = 6gives 9.n = 2,p = 12gives 4: 26, 34, 43 and 62.n = 2,p = 0gives 9.n = 1,p = 5gives 1.n = 12,p = 0gives 617570463519.