↓Skip to main content
  1. LeetCode/

LeetCode 2376: Count Special Integers

·2 mins· ·
LeetCode Hard Dynamic-Programming Digit-Dp Math Bitmask
Wei Yi Chung
Author
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
Table of Contents

Meta Data
#

Difficulty: hard First Attempt: 2026-09-05

  • Submissions: Accepted ×2

Approach
#

Count numbers in [1, n] whose digits are all distinct. A digit DP with state (pos, tight, started, mask):

  • mask: 10 bits of used digits; this position cannot choose a digit in mask.
  • started: leading zeros do not use up 0. Before starting, a 0 leaves mask unchanged; recording begins with the first non-zero digit.
  • At the end, only a started number counts as a positive integer.

My Solution
#

from functools import cache

class Solution:
    def countSpecialNumbers(self, n: int) -> int:
        s = str(n)

        @cache
        def dfs(i: int, tight: bool, started: bool, mask: int) -> int:
            if i == len(s):
                return 1 if started else 0
            hi = int(s[i]) if tight else 9
            count = 0
            for d in range(hi + 1):
                if mask >> d & 1:
                    continue                       # digit already used
                now_started = started or d != 0
                new_mask = mask | (1 << d) if now_started else mask   # leading zeros are not recorded
                count += dfs(i + 1, tight and d == hi, now_started, new_mask)
            return count

        return dfs(0, True, False, 0)

Complexity
#

At most 10 × 2 × 2 × 2^10 states with 10 digits each, about 4 × 10^5.

Mistakes to Avoid
#

  • Recording leading zeros as a used 0: numbers like 10 get misjudged.
  • Both mask and started must be part of the cache key.

Interview Explanation
#

A digit DP whose state is the position, whether it still matches n, whether the number has started, and a mask of used digits. A digit in the mask cannot be chosen; before starting, a 0 is not added to the mask, because leading zeros are not real digits. Only a started number counts at the end. There are 10 × 4 × 1024 states, very few.

Related

LeetCode 902: Numbers At Most N Given Digit Set
·2 mins
LeetCode Hard Dynamic-Programming Digit-Dp Math Binary-Search String
LeetCode 902 note: digit DP with state (pos, tight, started); before starting, a position can be skipped, representing numbers with fewer digits than n
LeetCode 2912: Number of Ways to Reach Destination in the Grid
·3 mins
LeetCode Hard Dynamic-Programming State-Machine Math Combinatorics
LeetCode 2912 note: many cells, but only four classes relative to the destination (itself, same row, same column, neither); each class’s total moves together in O(k)
LeetCode 3906: Count Good Integers on a Grid Path
·2 mins
LeetCode Hard Dynamic-Programming Digit-Dp
LeetCode 3906 note: the range reaches 9 × 10^15, so no per-number checks; a digit DP carries both lower and upper tight flags, plus the previous digit on the path since path cells must be non-decreasing
LeetCode 2163: Minimum Difference in Sums After Removal of Elements
·2 mins
LeetCode Hard Dynamic-Programming Heap Array
LeetCode 2163 note: try every boundary k between the two parts; the left takes the n smallest of nums[:k] (a max-heap) and the right the n largest of nums[k:] (a min-heap), one prefix and one suffix pass
LeetCode 2963: Count the Number of Good Partitions
·2 mins
LeetCode Hard Math Combinatorics Hash-Table Array
LeetCode 2963 note: all occurrences of a value must share a part, so last occurrences cut the array into b indivisible blocks; each boundary may be cut or not, giving 2^(b-1)
LeetCode 2328: Number of Increasing Paths in a Grid
·2 mins
LeetCode Hard Dynamic-Programming Grid-Dp Memoization Topological-Sort Matrix
LeetCode 2328 note: strictly increasing means no cycles; dp[cell] = 1 + Σ dp[larger neighbors], processed from largest to smallest value (or by memoized DFS), summed over all cells