A clear explanation of counting numbers with unique digits using combinatorics.
Problem Restatement
Given an integer n, count how many integers x satisfy:
0 <= x < 10**nand have no repeated digits.
A number has unique digits if every digit appears at most once.
Examples:
123 has unique digits
102 has unique digits
11 does not have unique digits
100 does not have unique digitsThe range includes 0.
The range does not include 10^n.
The problem constraints are 0 <= n <= 8. The official examples include n = 2, whose answer is 91, and n = 0, whose answer is 1.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer n |
| Output | Count of numbers with unique digits |
| Range | 0 <= x < 10^n |
| Constraint | 0 <= n <= 8 |
Example function shape:
def countNumbersWithUniqueDigits(n: int) -> int:
...Examples
For:
n = 0The range is:
0 <= x < 1Only the number 0 is included.
So the answer is:
1For:
n = 1The range is:
0 <= x < 10The numbers are:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9All have unique digits.
So the answer is:
10For:
n = 2The range is:
0 <= x < 100There are 100 numbers from 0 to 99.
The repeated-digit two-digit numbers are:
11, 22, 33, 44, 55, 66, 77, 88, 99There are 9 of them.
So the answer is:
100 - 9 = 91First Thought: Check Every Number
A direct solution is to loop through every number from 0 to 10^n - 1.
For each number:
- Convert it to a string.
- Check whether all characters are distinct.
- Count it if no digit repeats.
Example:
class Solution:
def countNumbersWithUniqueDigits(self, n: int) -> int:
limit = 10 ** n
count = 0
for x in range(limit):
digits = str(x)
if len(set(digits)) == len(digits):
count += 1
return countThis is easy to understand.
But it checks every number, so the time grows as 10^n.
For n = 8, that means checking 100,000,000 numbers.
We need to count directly instead.
Key Insight
Count valid numbers by their digit length.
For length 0, we count the number 0.
For length 1, the valid numbers are:
0 through 9There are 10.
For length 2, the first digit cannot be 0.
So:
| Position | Choices |
|---|---|
| First digit | 9 choices: 1 to 9 |
| Second digit | 9 choices: 0 to 9, except the first digit |
So length 2 contributes:
9 * 9 = 81For length 3:
| Position | Choices |
|---|---|
| First digit | 9 |
| Second digit | 9 |
| Third digit | 8 |
So length 3 contributes:
9 * 9 * 8In general, for a fixed length k >= 1:
first digit: 9 choices
second digit: 9 choices
third digit: 8 choices
fourth digit: 7 choices
...Then add counts for all lengths from 1 to n, plus the number 0.
Algorithm
If n == 0, return 1.
Otherwise:
- Start with
total = 10, which counts all one-digit numbers:0through9. - Let
current = 9, representing the count for the current length before adding the next digit. - Let
available = 9, representing how many choices remain for the next digit. - For every length from
2ton:- Update:
current *= available- Add
currenttototal. - Decrease
available.
- Return
total.
Since there are only 10 digits, lengths above 10 contribute nothing. For this problem, n <= 8, so this does not affect the official constraints.
Correctness
Every number in the range 0 <= x < 10^n has at most n digits.
The algorithm counts valid numbers by exact digit length.
The number 0 is included in the one-digit count.
For every length k >= 1, the first digit has 9 choices because it cannot be 0. Each later position must use a digit that has not appeared before. Therefore the number of valid k-digit numbers is counted by multiplying the number of choices at each position.
These length groups are disjoint because a number has exactly one decimal length, except 0, which is already included in the base count.
The algorithm sums the counts for all lengths up to n, so it counts every valid number in the required range exactly once.
Therefore, the returned value is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | One loop from length 2 to n |
| Space | O(1) | Only a few counters are stored |
With n <= 8, this is constant in practice.
Implementation
class Solution:
def countNumbersWithUniqueDigits(self, n: int) -> int:
if n == 0:
return 1
total = 10
current = 9
available = 9
for length in range(2, n + 1):
current *= available
total += current
available -= 1
return totalCode Explanation
The case n = 0 means the range is only:
0 <= x < 1So only 0 is counted:
if n == 0:
return 1For n >= 1, all one-digit numbers are valid:
total = 10The variable current stores how many valid numbers have the current length.
Before length 2, we start with:
current = 9This represents the first digit choices: 1 through 9.
The next digit has 9 choices:
available = 9For each new length:
current *= availableThis adds one more digit position.
Then:
total += currentadds all valid numbers of that length.
Finally:
available -= 1because one more digit has been used.
Testing
def run_tests():
s = Solution()
assert s.countNumbersWithUniqueDigits(0) == 1
assert s.countNumbersWithUniqueDigits(1) == 10
assert s.countNumbersWithUniqueDigits(2) == 91
assert s.countNumbersWithUniqueDigits(3) == 739
assert s.countNumbersWithUniqueDigits(4) == 5275
assert s.countNumbersWithUniqueDigits(8) == 2345851
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
n = 0 | Checks the special range [0, 1) |
n = 1 | All single-digit numbers are valid |
n = 2 | Official example |
n = 3 | Checks multiplication by decreasing choices |
n = 4 | Checks accumulated total |
n = 8 | Checks upper constraint |