Numbers At Most N Given Digit Set

hard digit dp combinatorics math

Problem

Given a sorted set of distinct non-zero digits and an integer n, return how many positive integers can be written using only those digits (each digit may be used any number of times) that are less than or equal to n.

Inputdigits = ["1", "3", "5", "7"], n = 100
Output20
The 1-digit numbers (4) plus the 2-digit numbers (4 × 4 = 16) all sum to 20; no valid 3-digit number is ≤ 100.

def at_most_n_given_digit_set(digits, n):
    s = str(n)
    k = len(s)
    m = len(digits)
    total = 0
    # Numbers with fewer digits than n: free choice of length.
    for length in range(1, k):
        total += m ** length
    # Numbers with exactly k digits, prefix-bounded by n.
    for i, ch in enumerate(s):
        smaller = sum(1 for d in digits if d < ch)
        total += smaller * (m ** (k - i - 1))
        if ch not in digits:
            return total          # cannot match this digit, stop
    return total + 1              # n itself is buildable
function atMostNGivenDigitSet(digits, n) {
  const s = String(n);
  const k = s.length, m = digits.length;
  let total = 0;
  for (let len = 1; len < k; len++) total += Math.pow(m, len);
  for (let i = 0; i < k; i++) {
    const ch = s[i];
    let smaller = 0;
    for (const d of digits) if (d < ch) smaller++;
    total += smaller * Math.pow(m, k - i - 1);
    if (!digits.includes(ch)) return total;
  }
  return total + 1;
}
class Solution {
    public int atMostNGivenDigitSet(String[] digits, int n) {
        String s = String.valueOf(n);
        int k = s.length(), m = digits.length, total = 0;
        for (int len = 1; len < k; len++) total += (int) Math.pow(m, len);
        for (int i = 0; i < k; i++) {
            char ch = s.charAt(i);
            int smaller = 0;
            boolean match = false;
            for (String d : digits) {
                if (d.charAt(0) < ch) smaller++;
                if (d.charAt(0) == ch) match = true;
            }
            total += smaller * (int) Math.pow(m, k - i - 1);
            if (!match) return total;
        }
        return total + 1;
    }
}
int atMostNGivenDigitSet(vector<string>& digits, int n) {
    string s = to_string(n);
    int k = s.size(), m = digits.size(), total = 0;
    for (int len = 1; len < k; len++) total += (int) pow(m, len);
    for (int i = 0; i < k; i++) {
        char ch = s[i];
        int smaller = 0; bool match = false;
        for (auto& d : digits) {
            if (d[0] < ch) smaller++;
            if (d[0] == ch) match = true;
        }
        total += smaller * (int) pow(m, k - i - 1);
        if (!match) return total;
    }
    return total + 1;
}
Time: O(k · m) Space: O(1)