class Solution:
def numDupDigitsAtMostN(self, N: int) -> int:
def count_unique(n):
digits = list(map(int, str(n + 1)))
n_digits = len(digits)
res = 0
# count numbers with length less than n_digits
for i in range(1, n_digits):
res += 9 * perm(9, i - 1)
# count numbers with length == n_digits
seen = set()
for i, x in enumerate(digits):
for y in range(0 if i else 1, x):
if y in seen:
continue
res += perm(9 - i, n_digits - i - 1)
if x in seen:
break
seen.add(x)
return res
def perm(m, n):
res = 1
for i in range(n):
res *= m - i
return res
return N - count_unique(N)
class Solution {
public:
int numDupDigitsAtMostN(int N) {
vector digits;
int n = N + 1;
while (n) {
digits.push_back(n % 10);
n /= 10;
}
reverse(digits.begin(), digits.end());
int n_digits = digits.size();
int res = 0;
// count numbers with length less than n_digits
for (int i = 1; i < n_digits; ++i) {
res += 9 * perm(9, i - 1);
}
// count numbers with length == n_digits
unordered_set<int> seen;
for (int i = 0; i < n_digits; ++i) {
for (int y = (i == 0 ? 1 : 0); y < digits[i]; ++y) {
if (seen.count(y)) continue;
res += perm(9 - i, n_digits - i - 1);
}
if (seen.count(digits[i])) break;
seen.insert(digits[i]);
}
return N - res;
}
int perm(int m, int n) {
int res = 1;
for (int i = 0; i < n; ++i) {
res *= (m - i);
}
return res;
}
};
class Solution {
public int numDupDigitsAtMostN(int N) {
int[] digits = toDigits(N + 1);
int nDigits = digits.length;
int res = 0;
// count numbers with length less than nDigits
for (int i = 1; i < nDigits; ++i) {
res += 9 * perm(9, i - 1);
}
// count numbers with length == nDigits
Set<Integer> seen = new HashSet<>();
for (int i = 0; i < nDigits; ++i) {
for (int y = (i == 0 ? 1 : 0); y < digits[i]; ++y) {
if (seen.contains(y)) continue;
res += perm(9 - i, nDigits - i - 1);
}
if (seen.contains(digits[i])) break;
seen.add(digits[i]);
}
return N - res;
}
private int[] toDigits(int n) {
String s = Integer.toString(n);
int[] res = new int[s.length()];
for (int i = 0; i < s.length(); ++i)
res[i] = s.charAt(i) - '0';
return res;
}
private int perm(int m, int n) {
int res = 1;
for (int i = 0; i < n; ++i)
res *= (m - i);
return res;
}
}
var numDupDigitsAtMostN = function(N) {
function perm(m, n) {
let res = 1;
for(let i = 0; i < n; ++i) res *= (m - i);
return res;
}
function countUnique(n) {
let digits = Array.from(String(n + 1), Number);
let nDigits = digits.length;
let res = 0;
// count numbers with length less than nDigits
for(let i = 1; i < nDigits; ++i) {
res += 9 * perm(9, i - 1);
}
// count numbers with length == nDigits
let seen = new Set();
for(let i = 0; i < nDigits; ++i) {
for(let y = (i === 0 ? 1 : 0); y < digits[i]; ++y) {
if(seen.has(y)) continue;
res += perm(9 - i, nDigits - i - 1);
}
if(seen.has(digits[i])) break;
seen.add(digits[i]);
}
return res;
}
return N - countUnique(N);
};
Given a positive integer N
, find how many positive integers less than or equal to N
have at least one repeated digit.
In other words, for a given N
, count all numbers x
such that 1 ≤ x ≤ N
and x
contains at least one digit that appears more than once in its decimal representation.
Constraints:
1 ≤ N ≤ 10^9
N
.
At first glance, it might seem reasonable to check each number from 1
to N
and see if it contains any repeated digits. For each number, we could convert it to a string or array of digits and check for duplicates. However, this approach is not practical for large values of N
(up to one billion), as it would take too long.
To optimize, we notice that it's easier to count the numbers without any repeated digits (i.e., with all unique digits), and then subtract this count from N
to get the answer. This is a classic application of the "complement principle" in combinatorics.
The challenging part is efficiently counting the numbers with all unique digits up to N
. We need to consider numbers with fewer digits than N
, as well as numbers with the same number of digits as N
but less than or equal to N
.
unique_count
), then subtract from N
:
result = N - unique_count
k
digits (1 ≤ k < number of digits in N
), the count is:
9 * P(9, k-1)
P(m, n)
is the number of ways to arrange n
digits from m
options (permutations), and the first digit can't be zero.
N
. This is done by traversing the digits of N
from left to right, at each step counting the choices for the next digit that haven't been used yet and that keep the number below N
.
P(m, n)
.N - unique_count
, which is the number of numbers with at least one repeated digit.
Let's walk through the process with N = 100
.
Step 1: Count numbers with all unique digits up to 100.
The repeated-digit numbers in this range are: 11, 22, 33, 44, 55, 66, 77, 88, 99, 100.
Brute-force approach:
This is efficient enough for N up to 109.
The key insight is to use the complement principle: instead of counting numbers with repeated digits directly, count the numbers with all unique digits and subtract from N. By using combinatorics and careful digit-by-digit traversal, we efficiently count unique-digit numbers up to N. This avoids brute-force enumeration, making the algorithm fast even for large N. The solution elegantly combines combinatorial counting with digit dynamic programming principles, making it both efficient and instructive.