Chaturmind
LearnDSASystem DesignBlogPremium
Sign inGet started
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML

Company

  • Blog
  • Premium
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Dynamic Programming Patterns

DP Fundamentals

  • Introduction to Dynamic Programming
  • 1D DP — Climbing Stairs to House Robber

String & Subsequence DP

  • Longest Common Subsequence
  • 0/1 Knapsack & Subsets

Advanced DP

  • Interval DP
  • State Machine DP
  • Digit DP
Chaturmind
← Dynamic Programming Patterns

DP Fundamentals

  • Introduction to Dynamic Programming
  • 1D DP — Climbing Stairs to House Robber

String & Subsequence DP

  • Longest Common Subsequence
  • 0/1 Knapsack & Subsets

Advanced DP

  • Interval DP
  • State Machine DP
  • Digit DP
HomeLearnDSADynamic Programming MasteryAdvanced DP
✓ FreeAdvanced· 14 min read

Digit DP

Count numbers in a range [lo, hi] satisfying digit constraints using digit DP with memoization.

Published March 21, 2025


Digit DP

Digit DP counts integers in a range [0, n] that satisfy some property about their digits (sum, distinct count, digit restrictions). It processes the number digit by digit, tracking whether we're still bounded by the original number.

Core Idea

For a number with d digits, we build it digit by digit. At each position, we can place a digit from 0 to 9, but if we're still "tight" (haven't placed a digit smaller than the corresponding digit in n), we're bounded above.

Count Numbers with Digit Sum ≤ K

public int countNumbersWithDigitSum(int n, int k) {
    String s = String.valueOf(n);
    int len = s.length();
    int[][] memo = new int[len][k+1]; // [position][remaining sum]
    // -1 = uncomputed; only valid when tight=false
    for (int[] row : memo) Arrays.fill(row, -1);
    return dp(s, 0, k, true, memo);
}

int dp(String s, int pos, int remainingSum, boolean tight, int[][] memo) {
    if (remainingSum < 0) return 0; // exceeded sum limit
    if (pos == s.length()) return 1; // valid number found

    if (!tight && memo[pos][remainingSum] != -1)
        return memo[pos][remainingSum];

    int limit = tight ? (s.charAt(pos) - '0') : 9;
    int count = 0;
    for (int digit = 0; digit <= limit; digit++) {
        count += dp(s, pos + 1, remainingSum - digit,
                    tight && digit == limit, memo);
    }

    if (!tight) memo[pos][remainingSum] = count;
    return count;
}

Count Numbers Without Digit 4 (or any forbidden digit)

public int countWithoutDigit(int n, int forbidden) {
    String s = String.valueOf(n);
    int[][] memo = new int[s.length()][2]; // [pos][hasLeadingZero]
    for (int[] row : memo) Arrays.fill(row, -1);
    return dp(s, 0, true, false, forbidden, memo);
}

int dp(String s, int pos, boolean tight, boolean leadingZero,
       int forbidden, int[][] memo) {
    if (pos == s.length()) return leadingZero ? 0 : 1;
    int key = tight ? -1 : (leadingZero ? 1 : 0); // simplified
    // Full memoization needs tight=false

    int limit = tight ? (s.charAt(pos) - '0') : 9;
    int count = 0;
    for (int d = 0; d <= limit; d++) {
        if (d == forbidden && !leadingZero) continue;
        count += dp(s, pos + 1, tight && d == limit,
                    leadingZero && d == 0, forbidden, memo);
    }
    return count;
}

Range Query: f(lo, hi) = f(hi) - f(lo-1)

// Most digit DP problems ask about range [lo, hi]
// Use: count(hi) - count(lo - 1)

public int countInRange(int lo, int hi) {
    return count(hi) - count(lo - 1);
}

Numbers with Same Digit Count

// Count numbers ≤ n where all digits are distinct
public int countNumbersWithUniqueDigits(int n) {
    // Special case: n ≤ 10 digits
    String s = String.valueOf(n);
    return dp(s, 0, true, false, 0);
}

int dp(String s, int pos, boolean tight, boolean started, int usedMask) {
    if (pos == s.length()) return started ? 1 : 0;
    int limit = tight ? (s.charAt(pos) - '0') : 9;
    int count = !started ? 1 : 0; // count the number 0 (leading zeros)
    for (int d = started ? 0 : 1; d <= limit; d++) {
        if (started && (usedMask >> d & 1) == 1) continue; // digit already used
        count += dp(s, pos+1, tight && d == limit,
                    true, usedMask | (1 << d));
    }
    return count;
}

Digit DP Template

State: (position, [...constraints...], tight, leadingZero)
Transition: try placing each valid digit at current position
Base case: pos == len → valid count (1 or 0)
Memoize: when tight=false (and leadingZero handled)

Interview Tips

  1. Digit DP appears in advanced interviews — knowing it signals strong DP fluency.
  2. The tight flag is the key insight: when tight=true, you're constrained by the original number's digit at this position.
  3. Only memoize states where tight=false — tight states are unique per call path.

Previous

State Machine DP

AI Tutor

Lesson: Digit DP

Quick actions

AI responses can be inaccurate. Verify critical information.