You are given a set of digits as an array of strings digits
(e.g. ["1","3","5","7"]
), and a positive integer n
. Your task is to count how many positive integers less than or equal to n
can be formed using only the digits in digits
. Each digit can be used multiple times, and numbers cannot have leading zeros.
digits
.n
.111
is valid if 1
is in digits
).n
.
At first glance, the problem might seem brute-force: generate all possible numbers using the given digits and count those ≤ n
. However, this is not feasible for large n
or larger digit sets, as the number of combinations grows rapidly.
The key insight is to recognize patterns and constraints:
n
, all combinations are valid.n
, we must ensure the constructed number does not exceed n
at each digit position.We solve the problem in two main parts:
n
:
l
(from 1 up to len(str(n)) - 1
), every combination is valid.len(digits) ** l
for each l
.n
:
n
at each step.digits
less than the current digit of n
, we can freely choose any digits for the remaining positions.digits
equals the current digit of n
, we continue to the next position.n
).n
itself can be formed with the given digits.
This approach avoids redundant computation and leverages combinatorics for efficiency.
Example:
digits = ["1", "3", "5", "7"]
, n = 100
1, 3, 5, 7
)11, 13, ..., 77
)n
has 3 digits)
n = 100
. Its digits are 1, 0, 0
.1
from digits
matches 1
.0
are allowed, but digits
has no 0
. So, no 3-digit numbers are valid.100
itself can be formed
1, 0, 0
. 0
is not in digits
.100
cannot be formed.n
using digits
, which is exponential: O(|digits|^len(n)
).l
less than len(n)
, we compute |digits|^l
(fast, not generating actual numbers).
n
, we process digit by digit, which is O(len(n) × |digits|
).
len(n) × |digits|
)
The problem is a classic example of digit dynamic programming and combinatorial counting. By separating numbers based on their length and using digit-by-digit comparison for numbers matching n
's length, we avoid brute-force enumeration and achieve an efficient solution. The approach leverages mathematical properties, reduces unnecessary computation, and is both elegant and scalable.
class Solution:
def atMostNGivenDigitSet(self, digits, n):
S = str(n)
K = len(S)
D = len(digits)
ans = 0
# Count numbers with length less than K
for i in range(1, K):
ans += D ** i
# Count numbers with length equal to K
for i in range(K):
has_same = False
for d in digits:
if d < S[i]:
ans += D ** (K - i - 1)
elif d == S[i]:
has_same = True
if not has_same:
return ans
return ans + 1
class Solution {
public:
int atMostNGivenDigitSet(vector<string>& digits, int n) {
string S = to_string(n);
int K = S.size(), D = digits.size(), ans = 0;
// Numbers with length less than K
for (int i = 1; i < K; ++i)
ans += pow(D, i);
// Numbers with length equal to K
for (int i = 0; i < K; ++i) {
bool has_same = false;
for (string d : digits) {
if (d[0] < S[i])
ans += pow(D, K - i - 1);
else if (d[0] == S[i])
has_same = true;
}
if (!has_same)
return ans;
}
return ans + 1;
}
};
class Solution {
public int atMostNGivenDigitSet(String[] digits, int n) {
String S = String.valueOf(n);
int K = S.length(), D = digits.length, ans = 0;
// Numbers with length less than K
for (int i = 1; i < K; ++i)
ans += Math.pow(D, i);
// Numbers with length equal to K
for (int i = 0; i < K; ++i) {
boolean hasSame = false;
for (String d : digits) {
if (d.charAt(0) < S.charAt(i))
ans += Math.pow(D, K - i - 1);
else if (d.charAt(0) == S.charAt(i))
hasSame = true;
}
if (!hasSame)
return ans;
}
return ans + 1;
}
}
var atMostNGivenDigitSet = function(digits, n) {
let S = n.toString();
let K = S.length, D = digits.length, ans = 0;
// Numbers with length less than K
for (let i = 1; i < K; ++i)
ans += Math.pow(D, i);
// Numbers with length equal to K
for (let i = 0; i < K; ++i) {
let hasSame = false;
for (let d of digits) {
if (d < S[i])
ans += Math.pow(D, K - i - 1);
else if (d === S[i])
hasSame = true;
}
if (!hasSame)
return ans;
}
return ans + 1;
};