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;
};