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.

LeetCode Problem 3032

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 size 3, same as string length 3, so it is valid.
  • 121 → {'1', '2'} has size 2, smaller than string length 3, 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

  1. Initialize a counter variable count to 0.

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:

  • 88 because digit 8 repeats
  • 99 because digit 9 repeats
  • 100 because 0 repeats
  • 101 because 1 repeats
  • 111 because 1 repeats

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.