↓Skip to main content
  1. LeetCode/

LeetCode 902: Numbers At Most N Given Digit Set

·2 mins· ·
LeetCode Hard Dynamic-Programming Digit-Dp Math Binary-Search String
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-07-27

  • Submissions: Accepted (first try)

Approach
#

Count positive integers <= n using only the given digits (no 0). A digit DP with state (pos, tight, started):

  • tight: whether the prefix still matches n. While tight, this digit must be <= n[pos].
  • started: whether the number has begun. Before starting, a position may be skipped, giving a number with fewer digits than n; that is always smaller than n, so tight becomes False.
  • At the end, only a started number counts (not the empty one).

My Solution
#

from functools import cache

class Solution:
    def atMostNGivenDigitSet(self, digits: List[str], n: int) -> int:
        s = str(n)

        @cache
        def dfs(i: int, tight: bool, started: bool) -> int:
            if i == len(s):
                return 1 if started else 0
            res = 0
            if not started:
                res += dfs(i + 1, False, False)    # skip: a number with fewer digits
            for d in digits:                       # digits are sorted
                if tight and d > s[i]:
                    break
                res += dfs(i + 1, tight and d == s[i], True)
            return res

        return dfs(0, True, False)

My accepted version did not check started at the end; it only allowed skipping before the last position (index != len - 1), which also avoids counting the empty number.

Alternatively, count directly: numbers with fewer digits than n total Σ |D|^k, and equal-length numbers are compared digit by digit.

Complexity
#

Time O(len(n) · |digits|), space O(len(n)).

Mistakes to Avoid
#

  • After skipping a position, tight must become False.
  • “Skip every position” is not a number.

Interview Explanation
#

A digit DP whose state is the position, whether it still matches n, and whether the number has started. Before starting, skipping a position represents a shorter number, which is always below n; once started, each position picks from the given digits, not exceeding n’s digit while tight. Only a started number counts at the end. Time O(len(n) · |digits|).

Related

LeetCode 1147: Longest Chunked Palindrome Decomposition
·2 mins
LeetCode Hard Dynamic-Programming Greedy Two-Pointers Rolling-Hash String
LeetCode 1147 note: build strings inward from both ends and cut a pair as soon as they match; cutting early is best, and any leftover middle counts as one chunk
LeetCode 488: Zuma Game
·3 mins
LeetCode Hard Dynamic-Programming Memoization Breadth-First-Search String Stack
LeetCode 488 note: with at most 16 balls on the board and 5 in hand, memoize DFS on the whole (board, hand) state, inserting only before a same-colored ball or between two equal balls
LeetCode 1745: Palindrome Partitioning IV
·2 mins
LeetCode Hard Dynamic-Programming Interval-Dp String
LeetCode 1745 note: build is_pal[i][j] with interval DP, then try both cut points; three palindromic parts means true, O(n^2)
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 3518: Smallest Palindromic Rearrangement II
·3 mins
LeetCode Hard Math Combinatorics Counting Hash-Table String
LeetCode 3518 note: a palindrome is determined by its left half, so this is the k-th permutation of a multiset; try characters position by position and use permutation counts, updated in O(1), to find k’s branch
LeetCode 1092: Shortest Common Supersequence
·5 mins
LeetCode Hard Dynamic-Programming String
LeetCode note for Shortest Common Supersequence, rebuilt from the original learning note