LeetCode 3032 - Count Numbers With Unique Digits II
The problem asks us to count how many integers in the inclusive range [a, b] contain only unique digits. A number has unique digits if no digit appears more than once within that number. For example, the number 123 has unique digits because 1, 2, and 3 each appear exactly once.
Difficulty: 🟢 Easy
Topics: Hash Table, Math, Dynamic Programming
Solution
Problem Understanding
The problem asks us to count how many integers in the inclusive range [a, b] contain only unique digits. A number has unique digits if no digit appears more than once within that number.
For example, the number 123 has unique digits because 1, 2, and 3 each appear exactly once. However, 121 does not have unique digits because the digit 1 appears twice. Similarly, 11 is invalid because both digits are the same.
The input consists of two positive integers, a and b, where a <= b. Our task is to examine every number in this interval and determine whether its digits are all distinct. The final result should be the total count of valid numbers.
The constraints are very important here:
1 <= a <= b <= 1000
This tells us the search space is extremely small. At most, we need to inspect numbers from 1 to 1000, which means at worst there are only 1000 values to process. Since each number has at most 4 digits, even a straightforward approach is computationally cheap.
An important observation is that we are not asked to generate numbers with unique digits, only to count them. Since the range size is small, checking each number independently is practical.
There are also several edge cases worth considering upfront. Numbers with repeated digits such as 11, 99, or 100 must be excluded. Single-digit numbers are always valid because they contain only one digit. The upper bound 1000 is interesting because it contains repeated zeros, making it invalid. The problem guarantees valid input ordering (a <= b), so we never need to handle reversed ranges.
Approaches
Brute Force Approach
The most direct way to solve this problem is to iterate through every number from a to b, convert the number into digits, and check whether any digit repeats.
One simple method is to convert the number into a string and compare the length of the string with the size of a set containing its characters. Since a set automatically removes duplicates, if the lengths are equal, all digits are unique.
For example:
123 → {'1', '2', '3'}has size3, same as string length3, so it is valid.121 → {'1', '2'}has size2, smaller than string length3, so it is invalid.
This approach is correct because every number is checked independently, and the uniqueness condition is verified exactly.
Although brute force may sound inefficient, the constraints are tiny. At worst, we examine 1000 numbers, each with at most 4 digits.
Optimal Approach
The key insight is that the brute-force solution is already efficient enough due to the small input size.
Instead of introducing unnecessary complexity such as digit dynamic programming, we can directly check digit uniqueness using a hash set. Since every number has at most 4 digits, the digit-check operation is effectively constant time.
Using a set gives us an elegant and efficient uniqueness test:
- Convert number to string
- Insert digits into a set
- Compare lengths
This minimizes implementation complexity while remaining extremely fast.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((b - a + 1) × d) | O(d) | Check every digit manually for repetition |
| Optimal | O((b - a + 1) × d) | O(d) | Use a hash set for concise uniqueness checking |
Here, d represents the number of digits in a number. Since d <= 4, this is effectively constant.
Algorithm Walkthrough
- Initialize a counter variable
countto0.
This variable keeps track of how many numbers in the range have unique digits.
2. Iterate through every integer from a to b, inclusive.
Since the problem asks us to evaluate every number in the interval, we process them one by one. 3. Convert the current number into a string.
Converting to a string allows easy access to individual digits without repeated division or modulo operations. 4. Create a set from the digits of the number.
A set stores only distinct elements. If a digit appears multiple times, duplicates are automatically removed. 5. Compare the length of the digit string with the size of the set.
If both lengths are equal, no duplicates exist, meaning the number contains unique digits.
6. Increment count whenever a valid number is found.
Each valid number contributes exactly one to the answer.
7. Return count after processing all numbers.
This gives the total number of integers with unique digits in the range.
Why it works
The algorithm works because for every number, the set contains exactly the distinct digits of that number. If a digit repeats, the set size becomes smaller than the total digit count. Therefore, equality between the two lengths guarantees digit uniqueness, and inequality guarantees repetition. Since every number in [a, b] is checked exactly once, the final count is correct.
Python Solution
class Solution:
def numberCount(self, a: int, b: int) -> int:
count = 0
for number in range(a, b + 1):
digits = str(number)
if len(digits) == len(set(digits)):
count += 1
return count
The implementation follows the algorithm directly.
We begin by initializing count to zero. Then, we iterate through every number in the inclusive range [a, b].
For each number, we convert it into a string so that each digit becomes easy to access. We create a set from the string, which automatically removes duplicate digits.
The condition:
len(digits) == len(set(digits))
checks whether duplicates exist. If both lengths are equal, all digits are unique, so we increment the answer.
Finally, after examining all numbers, we return the accumulated count.
Go Solution
func numberCount(a int, b int) int {
count := 0
for number := a; number <= b; number++ {
digits := []rune(fmt.Sprintf("%d", number))
seen := make(map[rune]bool)
isUnique := true
for _, digit := range digits {
if seen[digit] {
isUnique = false
break
}
seen[digit] = true
}
if isUnique {
count++
}
}
return count
}
The Go version follows the same logic but uses a map[rune]bool to simulate a hash set because Go does not provide a built-in set type.
Each number is converted into a string using fmt.Sprintf, then iterated character by character. If a digit has already been seen, the number is marked invalid and processing stops early.
One practical difference is that Go requires importing the fmt package:
import "fmt"
Integer overflow is not a concern because the maximum value is only 1000.
Worked Examples
Example 1
Input: a = 1, b = 20
We iterate through every number from 1 to 20.
| Number | Digits | Unique Set | Valid? | Count |
|---|---|---|---|---|
| 1 | "1" | {1} | Yes | 1 |
| 2 | "2" | {2} | Yes | 2 |
| ... | ... | ... | ... | ... |
| 10 | "10" | {1,0} | Yes | 10 |
| 11 | "11" | {1} | No | 10 |
| 12 | "12" | {1,2} | Yes | 11 |
| ... | ... | ... | ... | ... |
| 20 | "20" | {2,0} | Yes | 19 |
Final answer: 19
Example 2
Input: a = 9, b = 19
| Number | Digits | Unique? | Count |
|---|---|---|---|
| 9 | "9" | Yes | 1 |
| 10 | "10" | Yes | 2 |
| 11 | "11" | No | 2 |
| 12 | "12" | Yes | 3 |
| 13 | "13" | Yes | 4 |
| 14 | "14" | Yes | 5 |
| 15 | "15" | Yes | 6 |
| 16 | "16" | Yes | 7 |
| 17 | "17" | Yes | 8 |
| 18 | "18" | Yes | 9 |
| 19 | "19" | Yes | 10 |
Final answer: 10
Example 3
Input: a = 80, b = 120
Some invalid numbers include:
88because digit8repeats99because digit9repeats100because0repeats101because1repeats111because1repeats
The algorithm checks all 41 numbers in the range and finds 27 valid numbers.
Final answer: 27
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O((b - a + 1) × d) | We process each number once and inspect all its digits |
| Space | O(d) | The set stores at most the digits of one number |
Since d <= 4, the complexity is effectively linear in the size of the range. Even in the worst case, we examine only 1000 numbers, making the solution extremely efficient.
Test Cases
solution = Solution()
assert solution.numberCount(1, 20) == 19 # Example 1
assert solution.numberCount(9, 19) == 10 # Example 2
assert solution.numberCount(80, 120) == 27 # Example 3
assert solution.numberCount(1, 1) == 1 # Smallest valid input
assert solution.numberCount(11, 11) == 0 # Single repeated-digit number
assert solution.numberCount(98, 102) == 2 # Mixed valid and invalid values
assert solution.numberCount(1000, 1000) == 0 # Repeated zeros
assert solution.numberCount(123, 123) == 1 # Single valid number
assert solution.numberCount(999, 1000) == 0 # Entire range invalid
assert solution.numberCount(10, 15) == 6 # All numbers unique
assert solution.numberCount(22, 25) == 3 # One repeated-digit number
| Test | Why |
|---|---|
(1, 20) |
Verifies Example 1 |
(9, 19) |
Verifies Example 2 |
(80, 120) |
Verifies Example 3 |
(1, 1) |
Tests smallest boundary |
(11, 11) |
Tests repeated-digit exclusion |
(98, 102) |
Tests crossing digit lengths |
(1000, 1000) |
Tests repeated zero handling |
(123, 123) |
Tests single valid number |
(999, 1000) |
Tests fully invalid range |
(10, 15) |
Tests all-valid interval |
(22, 25) |
Tests mixed validity |
Edge Cases
One important edge case is a range containing only a single number. For example, a = b = 11. A buggy implementation might assume ranges always contain multiple values or incorrectly count the number without checking uniqueness. Our implementation still loops once and correctly identifies that 11 contains repeated digits, returning 0.
Another important case involves repeated zeros, such as 100 or 1000. These numbers can be tricky because leading zeros are not part of the representation, but repeated internal zeros still count as duplicates. By converting the number to a string and using a set, repeated zeros are naturally detected. For example, "100" becomes {'1', '0'}, whose size differs from the string length.
A third edge case occurs when the range crosses digit lengths, such as 98 to 102. Some implementations may accidentally mishandle transitions between two-digit and three-digit numbers. Our solution treats every number independently, so numbers like 98 and 102 are processed correctly regardless of digit count.
Finally, ranges containing only invalid numbers, such as [999, 1000], must return 0. Since the counter increases only when uniqueness is confirmed, the implementation naturally handles this scenario without any special-case logic.