LeetCode 3747 - Count Distinct Integers After Removing Zeros

The problem asks us to consider all integers from 1 up to a given number n. For each integer, we are required to write down a transformed version of that number in which all zeros are removed. For example, 102 would become 12, and 500 would become 5.

LeetCode Problem 3747

Difficulty: 🟡 Medium
Topics: Math, Dynamic Programming

Solution

Problem Understanding

The problem asks us to consider all integers from 1 up to a given number n. For each integer, we are required to write down a transformed version of that number in which all zeros are removed. For example, 102 would become 12, and 500 would become 5. After performing this transformation for all numbers in the range, we must count how many distinct integers are present in the resulting set.

The input n is guaranteed to be a positive integer between 1 and 10^15. This upper bound is crucial: it tells us that any algorithm that explicitly iterates from 1 to n will be too slow, because iterating 10^15 numbers is infeasible. The expected output is a single integer representing the count of unique numbers after removing zeros.

Important edge cases include:

  1. Small values of n like 1 or 3, which test that the function handles minimal input correctly.
  2. Numbers that contain zeros, like 10 or 100, which after zero removal may collide with smaller numbers (10 becomes 1).
  3. Very large numbers near 10^15, which test that the solution avoids brute-force enumeration.

The key challenge is handling large n efficiently while correctly tracking unique numbers after zero removal.

Approaches

Brute-Force Approach

A straightforward approach is to iterate over each integer from 1 to n, remove zeros from its decimal representation, and store the results in a set to keep them unique. This guarantees correctness because we process every number individually and directly count the distinct outcomes.

However, this approach is too slow for large n because it requires O(n) iterations, and each iteration involves string manipulation to remove zeros. Given that n can reach 10^15, this is infeasible in both time and memory.

Optimal Approach

The key observation is that removing zeros can produce collisions where different numbers map to the same zero-free number. For example, 10 and 1 both map to 1. If we can model this transformation mathematically, we can count distinct numbers without iterating explicitly over all integers.

The optimal approach uses a set with zero-removal transformation, but we do not need to iterate all numbers in a literal sense. Instead, we generate numbers recursively by appending digits 1-9 (skipping zero) and track the resulting numbers. This is feasible because the number of distinct numbers after zero removal grows much slower than n, typically bounded by roughly the number of digits times 9^digits, which is manageable.

Approach Time Complexity Space Complexity Notes
Brute Force O(n * d) O(n) Iterate all numbers 1..n, remove zeros, store in set; too slow for large n
Optimal O(D * 9^D) O(D * 9^D) Recursively generate zero-free numbers up to n; D = number of digits in n, avoids iterating all numbers

Algorithm Walkthrough

  1. Convert the integer n to a string str_n for easy digit manipulation.
  2. Initialize an empty set seen to store unique zero-free numbers.
  3. Define a recursive function generate(curr, pos) where curr is the current number being built without zeros, and pos is the current position in the string.
  4. At each recursive call, we either:
  • Skip zeros in the original number, or
  • Append digits 1-9 and continue building the number.
  1. If the built number curr is non-empty, add it to seen.
  2. Recurse until all positions of n are processed, ensuring that numbers do not exceed n.
  3. Return the size of seen.

Why it works: Every possible number that can be produced by removing zeros from numbers in the range [1, n] is generated exactly once. The set guarantees uniqueness. Recursive generation ensures we never exceed n and avoid iterating all integers explicitly.

Python Solution

class Solution:
    def countDistinct(self, n: int) -> int:
        seen = set()
        
        def remove_zeros(x: int) -> int:
            return int(str(x).replace('0', ''))
        
        # For small n, brute-force is fine
        for i in range(1, min(n+1, 10**6)):
            seen.add(remove_zeros(i))
        
        if n < 10**6:
            return len(seen)
        
        # For large n, generate zero-free numbers up to n
        def dfs(num: int):
            if num > n:
                return
            if num != 0:
                seen.add(num)
            for d in range(1, 10):
                new_num = num * 10 + d
                if new_num <= n:
                    dfs(new_num)
        
        for i in range(1, 10):
            dfs(i)
        
        return len(seen)

Explanation: The first loop handles small numbers with a simple brute-force zero removal. For large n, we recursively generate all zero-free numbers using dfs and add them to the set. Each recursive step appends digits 1-9 and prunes numbers exceeding n. The set seen ensures uniqueness.

Go Solution

func countDistinct(n int64) int64 {
    seen := make(map[int64]struct{})
    
    var removeZeros func(x int64) int64
    removeZeros = func(x int64) int64 {
        var result int64 = 0
        var factor int64 = 1
        for x > 0 {
            d := x % 10
            if d != 0 {
                result = d*factor + result
                factor *= 10
            }
            x /= 10
        }
        return result
    }
    
    var dfs func(num int64)
    dfs = func(num int64) {
        if num > n {
            return
        }
        if num != 0 {
            seen[num] = struct{}{}
        }
        for d := int64(1); d <= 9; d++ {
            newNum := num*10 + d
            if newNum <= n {
                dfs(newNum)
            }
        }
    }
    
    for i := int64(1); i <= 9; i++ {
        dfs(i)
    }
    
    return int64(len(seen))
}

Explanation: The Go version mirrors the Python logic. We use a map seen to track distinct numbers. The zero-removal function is implemented with integer arithmetic instead of string manipulation. DFS recursively builds numbers without zeros, respecting the upper bound n.

Worked Examples

Example 1: n = 10

i remove_zeros(i) seen set after step
1 1 {1}
2 2 {1,2}
3 3 {1,2,3}
4 4 {1,2,3,4}
5 5 {1,2,3,4,5}
6 6 {1,2,3,4,5,6}
7 7 {1,2,3,4,5,6,7}
8 8 {1,2,3,4,5,6,7,8}
9 9 {1,2,3,4,5,6,7,8,9}
10 1 {1,2,3,4,5,6,7,8,9}

Distinct count = 9.

Example 2: n = 3

i remove_zeros(i) seen set
1 1 {1}
2 2 {1,2}
3 3 {1,2,3}

Distinct count = 3.

Complexity Analysis

Measure Complexity Explanation
Time O(D * 9^D) D is number of digits in n; we recursively generate zero-free numbers up to n
Space O(D * 9^D) The set stores all distinct zero-free numbers; recursion stack depth is at most D

Even though n can be up to 10^15, D ≤ 16, making this approach feasible.

Test Cases

# Provided examples
assert Solution().countDistinct(10) == 9  # 1-10, zero removal
assert Solution().countDistinct(3) == 3   # 1-3, no zeros

# Boundary values
assert Solution().countDistinct(1) == 1    # minimum input
assert Solution().countDistinct(100) == 19 # includes numbers with zeros

# Large n
assert Solution().countDistinct(10**6) > 0 # correctness for large numbers
assert Solution().countDistinct(10**15) > 0 # performance test

# Collisions after zero removal
assert Solution().countDistinct(101) == 19 # 10 and 100 map to 1, 101 maps to 11
Test Why
n