Count Numbers with Unique Digits
MedPrompt
Given an integer n, return the count of all numbers with unique digits, x, where 0 <= x < 10n.
Example 1:
Input: n = 2
Output: 91
Explanation: The answer should be the total numbers in the range of 0 ≤ x < 100, excluding 11,22,33,44,55,66,77,88,99Example 2:
Input: n = 0
Output: 1
Constraints:
0 <= n <= 8
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves iterating through every number from 0 up to 10^n - 1 and checking if each number has unique digits. A helper function is used to determine if a number's digits are all distinct.
Algorithm
- Initialize a counter
countto 0. - Calculate the upper limit of the range,
limit = 10^n. - Iterate with a variable
ifrom 0 tolimit - 1. - For each
i, call a helper functionhasUniqueDigits(i).- Inside
hasUniqueDigits(num):- Create a boolean array
seenof size 10, initialized tofalse. - Repeatedly take the last digit of
num(num % 10). - If the digit has been seen before (i.e.,
seen[digit]istrue), returnfalse. - Mark the digit as seen by setting
seen[digit]totrue. - Update
numby dividing it by 10. - If the loop completes, all digits are unique, so return
true.
- Create a boolean array
- Inside
- If
hasUniqueDigits(i)returnstrue, incrementcount. - After the loop, return
count.
Walkthrough
We loop through all integers x in the range [0, 10^n). For each integer x, we check if its digits are unique. To check for uniqueness, we can convert the number to a sequence of its digits. A boolean array or a hash set can be used to keep track of the digits seen so far. If a digit is encountered more than once, the number does not have unique digits. We maintain a counter, which is incremented for every number that satisfies the unique digit property. The final value of the counter is the result.
class Solution { private boolean hasUniqueDigits(int n) { boolean[] seen = new boolean[10]; String s = String.valueOf(n); for (char c : s.toCharArray()) { if (seen[c - '0']) { return false; } seen[c - '0'] = true; } return true; } public int countNumbersWithUniqueDigits(int n) { if (n > 10) { n = 10; } int limit = (int) Math.pow(10, n); int count = 0; for (int i = 0; i < limit; i++) { if (hasUniqueDigits(i)) { count++; } } return count; }}Complexity
Time
O(n * 10^n). The loop runs `10^n` times. The check for unique digits takes `O(log10(i))` time, which is at most `O(n)`. This approach is very slow and will time out for larger values of `n`.
Space
O(1). The space used by the `seen` array is constant (size 10).
Trade-offs
Pros
Simple to understand and implement.
Directly follows the problem definition.
Cons
Extremely inefficient.
Will not pass for
ngreater than 6 or 7 due to Time Limit Exceeded errors.
This approach builds numbers with unique digits recursively. We can think of it as exploring a decision tree where each level corresponds to a digit's position, and each branch is a choice of a digit (0-9) that has not been used yet.
Algorithm
- The problem is to count numbers
xin0 <= x < 10^nwith unique digits. - This is equivalent to counting all numbers with unique digits that have a length from 1 to
n. - We can define a recursive function, say
backtrack(current_number, used_digits_mask). - The function explores adding a new digit to
current_number. - Start with
count = 1(for the number 0). - The initial calls to backtracking would be for single-digit numbers (1 to 9).
backtrack(current_num, max_val, used_mask):count = 1(forcurrent_numitself).- For each digit
dfrom 0 to 9:- If
dis not inused_mask:- Form
next_num = current_num * 10 + d. - If
next_num < max_val:- Add the result of
backtrack(next_num, max_val, new_mask)tocount.
- Add the result of
- Form
- If
- The initial calls would be
backtrack(i, 10^n, mask_for_i)forifrom 1 to 9.
Walkthrough
The core idea is to count the valid numbers by constructing them. A number is a sequence of digits. We can use a backtracking function to explore all possible valid sequences. The state of our backtracking function can be (current_length, used_digits_mask). current_length is the number of digits in the number being built, and used_digits_mask is a bitmask representing the set of digits already used. The total count is the sum of counts for numbers of length 1, 2, ..., up to n. We must handle the leading zero case: a number cannot start with 0 unless it is the number 0 itself.
class Solution { public int countNumbersWithUniqueDigits(int n) { if (n > 10) { return countNumbersWithUniqueDigits(10); } // Count numbers with length < n // Start with 1 for the number 0 int count = 1; long max = (long) Math.pow(10, n); boolean[] used = new boolean[10]; // Count numbers with length 1 to n starting with 1-9 for (int i = 1; i <= 9; i++) { used[i] = true; count += backtrack(i, max, used); used[i] = false; } return count; } private int backtrack(long currentNum, long max, boolean[] used) { int count = 0; if (currentNum < max) { count = 1; } else { return 0; } for (int i = 0; i <= 9; i++) { if (!used[i]) { used[i] = true; long nextNum = currentNum * 10 + i; if (nextNum < max) { count += backtrack(nextNum, max, used); } used[i] = false; } } return count; }}Complexity
Time
The number of states is related to the number of valid prefixes. For `n=8`, this is `1 + 9 + 9*9 + 9*9*8 + ...`. This is much smaller than `10^n`. The complexity is roughly proportional to the number of unique-digit numbers, which is the answer itself.
Space
O(n) for the recursion depth.
Trade-offs
Pros
More efficient than brute force.
Explores only the valid search space of numbers with unique digits.
Cons
More complex to implement correctly than brute force.
Can be less intuitive than the direct combinatorial approach.
The recursive implementation might have overhead.
This is the most efficient approach, leveraging mathematical principles of permutations and combinations. We can calculate the count of unique-digit numbers for each possible length (from 1 to n) and sum them up.
Algorithm
- Handle the base case: if
n = 0, return 1. - Initialize
totalCount = 10(this covers all 1-digit numbers with unique digits). - Initialize
uniqueDigitsForK = 9(this represents the number of ways to choose the first digit for numbers with length > 1, i.e., from 1-9). - Initialize
availableChoices = 9(this represents the number of choices for the second digit). - Loop for length
kfrom 2 up ton(or 10, whichever is smaller, as numbers with more than 10 digits cannot have unique digits).- In each iteration, calculate the count of unique-digit numbers of length
k:uniqueDigitsForK = uniqueDigitsForK * availableChoices. - Add this count to
totalCount. - Decrement
availableChoicesfor the next iteration (as one more digit is now used).
- In each iteration, calculate the count of unique-digit numbers of length
- Return
totalCount.
Walkthrough
Let f(k) be the count of numbers with unique digits of length k. The total count for a given n is the sum of f(k) for k from 1 to n.
- For
k=1: The numbers are 0, 1, ..., 9. All 10 have unique digits. So, the count is 10. - For
k=2: A 2-digit numberabhasain {1..9} (9 choices) andbin {0..9} whereb != a(9 choices). So, the count is9 * 9 = 81. - For
k=3: A 3-digit numberabchasain {1..9} (9 choices),bin {0..9} \ {a} (9 choices), andcin {0..9} \ {a, b} (8 choices). So, the count is9 * 9 * 8 = 648. - In general, for
k > 1, the count is9 * P(9, k-1) = 9 * 9 * 8 * ... * (10 - k + 1). We can observe a recurrence relation. The count for lengthkcan be derived from the count for lengthk-1. We can compute these values iteratively and add them to a running total.
class Solution { public int countNumbersWithUniqueDigits(int n) { if (n == 0) { return 1; } // The problem asks for numbers in the range [0, 10^n). // This is equivalent to counting numbers with unique digits of length 1, 2, ..., n. // For n=1, the numbers are 0, 1, ..., 9. All 10 have unique digits. int totalCount = 10; // For numbers with length k > 1. // The first digit has 9 choices (1-9). // The second digit has 9 choices (0-9, excluding the first). // The third digit has 8 choices. // And so on. int uniqueDigitsForK = 9; // Count for length k, starting with k=2. First digit has 9 choices. int availableChoices = 9; // Choices for subsequent digits. // Loop for lengths from 2 to n. // Math.min(n, 10) because for n > 10, no new unique digit numbers can be formed. for (int k = 2; k <= Math.min(n, 10); k++) { uniqueDigitsForK = uniqueDigitsForK * availableChoices; totalCount += uniqueDigitsForK; availableChoices--; } return totalCount; }}Complexity
Time
O(n). The loop runs at most `n-1` times. Since `n` is small (`<= 8`), this is effectively constant time, O(1).
Space
O(1). We only use a few variables to store the intermediate and final counts.
Trade-offs
Pros
Extremely efficient in both time and space.
Provides a direct calculation without unnecessary exploration.
Simple and concise implementation.
Cons
Requires understanding the underlying combinatorial logic, which might be less obvious than a brute-force approach.
Solutions
Solution
class Solution {public int countNumbersWithUniqueDigits(int n) { if (n == 0) { return 1; } if (n == 1) { return 10; } int ans = 10; for (int i = 0, cur = 9; i < n - 1; ++i) { cur *= (9 - i); ans += cur; } return ans; }}class Solution: def countNumbersWithUniqueDigits(self, n: int) -> int: if n == 0: return 1 if n == 1: return 10 ans, cur = 10, 9 for i in range(n - 1): cur *= 9 - i ans += cur return ansclass Solution {public: int countNumbersWithUniqueDigits(int n) { if (n == 0) return 1; if (n == 1) return 10; int ans = 10; for (int i = 0, cur = 9; i < n - 1; ++i) { cur *= (9 - i); ans += cur; } return ans; }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.