Digit DP | Introduction

Last Updated : 20 Jul, 2026

Digit DP is a Dynamic Programming technique used to solve problems that involve counting, summing, or finding numbers within a given range whose properties depend on their digits. Many problems ask questions such as:

  • Count numbers whose digit sum is equal to k.
  • Count numbers that do not contain a particular digit.
  • Find the sum of digits of all numbers in a range.
  • Count numbers divisible by a given value.

When the range is very large (up to 10^18 or more), checking every number individually becomes impossible. Digit DP helps solve such problems efficiently by processing numbers digit by digit.

Digit DP is a Dynamic Programming approach where we build numbers digit by digit while maintaining information about the digits chosen so far. Instead of iterating through all numbers, we calculate the answer directly by considering each digit position and storing intermediate results in DP states.

For range-based problems, Digit DP generally follows the formula: F(l, r) = G(r) - G(l - 1), where G(x) represents the answer for all numbers from 0 to x. So, the problem of finding the answer in a range [l, r] is reduced to solving the problem from 0 to x.

Given four integers a, b, d, and k, count how many numbers in the range [a, b] contain the digit d exactly k times.

Examples:

Input: a = 50, b = 250, d = 2, k = 2
Output: 14
Explanation: The numbers in the range [50, 250] where digit 2 appears exactly 2 times are: 122, 202, 212, 220, 221, 223, 224,225, 226, 227, 228, 229, 232, 242. Hence, the answer is 14.

Input: a = 100, b = 500, d = 3, k = 1
Output: 135
Explanation: We need to count numbers between 100 and 500 in which digit 3 appears exactly once. Some valid numbers are: 103, 113, 123, 130, 134, 203, 230, 301, 430, …
Examples that are not valid:
133  (digit 3 appears twice)
303  (digit 3 appears twice)
333  (digit 3 appears three times)
The total count of numbers containing digit 3 exactly once in the range [100, 500] is 135.

Check Every Number One by One - O((b - a + 1) * log b) Time O(1) Space

The idea is to iterate through every number from a to b and count the occurrences of digit d in each number. If the count becomes exactly k, then increment the answer.

Working of Approach:

  • Traverse every number from a to b and process each number independently.
  • For each number, repeatedly extract its last digit using % 10 and compare it with the target digit d.
  • Count how many times d appears by removing digits one by one using integer division (/ 10).
  • If the total occurrences of d are exactly k, increment the answer count.
  • After checking all numbers in the range, return the total count of valid numbers.
C++
#include <iostream>
using namespace std;

// Function to count occurrences of digit d in num
int countOccurrences(int num, int d)
{

    // Special case when num is 0
    if (num == 0)
        return (d == 0);

    int cnt = 0;

    while (num > 0)
    {
        if (num % 10 == d)
            cnt++;

        num /= 10;
    }

    return cnt;
}

// Function to count valid numbers in range [a, b]
int countNumbers(int a, int b, int d, int k)
{
    int ans = 0;

    // Traverse all numbers in the range
    for (int num = a; num <= b; num++)
    {

        // Count occurrences of digit d
        if (countOccurrences(num, d) == k)
            ans++;
    }

    return ans;
}

// Driver Code
int main()
{
    int a = 100, b = 500, d = 3, k = 1;

    cout << countNumbers(a, b, d, k);

    return 0;
}
Java
import java.util.Scanner;

// Function to count occurrences of digit d in num
public class GfG {
    public static int countOccurrences(int num, int d)
    {
        // Special case when num is 0
        if (num == 0)
            return (d == 0) ? 1 : 0;

        int cnt = 0;

        while (num > 0) {
            if (num % 10 == d)
                cnt++;

            num /= 10;
        }

        return cnt;
    }
    // Function to count valid numbers in range [a, b]
    public static int countNumbers(int a, int b, int d,
                                   int k)
    {
        int ans = 0;

        // Traverse all numbers in the range
        for (int num = a; num <= b; num++) {

            // Count occurrences of digit d
            if (countOccurrences(num, d) == k)
                ans++;
        }

        return ans;
    }

    // Driver Code
    public static void main(String[] args)
    {
        int a = 100, b = 500, d = 3, k = 1;

        System.out.println(countNumbers(a, b, d, k));
    }
}
Python
# Function to count occurrences of digit d in num
def countOccurrences(num, d):

    # Special case when num is 0
    if num == 0:
        return (d == 0)

    cnt = 0

    while num > 0:
        if num % 10 == d:
            cnt += 1

        num //= 10

    return cnt


# Function to count valid numbers in range [a, b]
def countNumbers(a, b, d, k):
    ans = 0

    # Traverse all numbers in the range
    for num in range(a, b + 1):

        # Count occurrences of digit d
        if countOccurrences(num, d) == k:
            ans += 1

    return ans


if __name__ == "__main__":
    a = 100
    b = 500
    d = 3
    k = 1

    print(countNumbers(a, b, d, k))
C#
using System;

// Function to count occurrences of digit d in num
public class GfG {
    // Function to count occurrences of digit d in num
    public static int countOccurrences(int num, int d)
    {
        // Special case when num is 0
        if (num == 0)
            return (d == 0) ? 1 : 0;

        int cnt = 0;

        while (num > 0) {
            if (num % 10 == d)
                cnt++;

            num /= 10;
        }

        return cnt;
    }

    // Function to count valid numbers in range [a, b]
    public static int countNumbers(int a, int b, int d,
                                   int k)
    {
        int ans = 0;

        // Traverse all numbers in the range
        for (int num = a; num <= b; num++) {
            // Count occurrences of digit d
            if (countOccurrences(num, d) == k)
                ans++;
        }

        return ans;
    }

    // Driver Code
    public static void Main()
    {
        int a = 100, b = 500, d = 3, k = 1;

        Console.WriteLine(countNumbers(a, b, d, k));
    }
}
JavaScript
// Function to count occurrences of digit d in num
function countOccurrences(num, d)
{

    // Special case when num is 0
    if (num === 0)
        return (d === 0);

    let cnt = 0;

    while (num > 0) {
        if (num % 10 === d)
            cnt++;

        num = Math.floor(num / 10);
    }

    return cnt;
}

// Function to count valid numbers in range [a, b]
function countNumbers(a, b, d, k)
{
    let ans = 0;

    // Traverse all numbers in the range
    for (let num = a; num <= b; num++) {

        // Count occurrences of digit d
        if (countOccurrences(num, d) === k)
            ans++;
    }

    return ans;
}

// Driver Code
let a = 100, b = 500, d = 3, k = 1;

console.log(countNumbers(a, b, d, k));

Output
135

Time Complexity: O((b - a + 1) * log b), as for each number we may traverse all its digits.
Auxiliary Space: O(1)

Using Digit DP - O(d * k * 2 *10) Time and O(d * k) Space

A digit dynamic programming solution is typically represented using a state of the form: dp(pos, tight, state), where pos and tight are common to almost every Digit DP problem, while state stores problem-specific information such as digit sum, remainder, count of occurrences, previous digit, etc.

For example, in a problem that asks for the sum of digits, the state can be: dp(pos, sum, tight), where:

  • pos: Current digit position being processed.
  • sum: Sum of digits chosen so far.
  • tight: Indicates whether the current digit is restricted by the given number.

The tight variable indicates whether the number formed so far is exactly equal to the prefix of the upper bound.

  • If the previous state is already smaller (tight = 0), the next state also remains smaller.
  • If the previous state is tight (tight = 1), then the next state remains tight only when the chosen digit equals the corresponding digit of the upper bound.
  • newTight = tight && (dig == digit[pos]);

The idea is to count how many numbers from 0 to x contain digit d exactly k times using Digit DP, and then get the answer for the range [a, b] as: count(0, b) - count(0, a - 1)

We process the digits of the number from left to right and maintain three states:

  • pos -> Current digit position being processed.
  • cnt -> Number of times digit d has appeared so far.
  • tight -> Indicates whether the current number's prefix is equal to the prefix of the upper bound (1) or already smaller (0).

For each position, we try all possible digits from 0 to the allowed limit. If the chosen digit equals d, we increment the occurrence count. The tight state is updated accordingly. When all positions are processed, the number is valid only if the count of digit d is exactly k. Memoization is used on (pos, cnt, tight) to avoid recomputing overlapping states, making the solution efficient.

C++
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;

// C++ program to count numbers in range [a, b]
// where digit d occurs exactly k times
// using Digit DP

vector<int> digit;
int dp[20][20][2];

int d, k;

// Function to count valid numbers from current state
int countWays(int pos, int cnt, int tight)
{

    // More than required occurrences
    if (cnt > k)
        return 0;

    // All digits processed
    if (pos == digit.size())
        return (cnt == k);

    // Return already computed result
    if (dp[pos][cnt][tight] != -1)
        return dp[pos][cnt][tight];

    int limit = (tight ? digit[pos] : 9);

    int res = 0;

    // Try all possible digits
    for (int dig = 0; dig <= limit; dig++)
    {

        int newCnt = cnt;

        if (dig == d)
            newCnt++;

        int newTight = tight && (dig == digit[pos]);

        res += countWays(pos + 1, newCnt, newTight);
    }

    return dp[pos][cnt][tight] = res;
}

// Returns count of valid numbers from 0 to x
int solve(int x)
{

    if (x < 0)
        return 0;

    digit.clear();

    // Store digits of x
    if (x == 0)
        digit.push_back(0);

    while (x > 0)
    {
        digit.push_back(x % 10);
        x /= 10;
    }

    reverse(digit.begin(), digit.end());

    memset(dp, -1, sizeof(dp));

    return countWays(0, 0, 1);
}

// Returns count of valid numbers in range [a, b]
int countNumbers(int a, int b, int digitToCount, int requiredCount)
{

    d = digitToCount;
    k = requiredCount;

    return solve(b) - solve(a - 1);
}

// Driver Code
int main()
{

    int a = 100, b = 500;
    int d = 3, k = 1;

    cout << countNumbers(a, b, d, k);

    return 0;
}
Java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class GfG {

    static ArrayList<Integer> digit = new ArrayList<>();
    static int[][][] dp = new int[20][20][2];

    static int d, k;

    // Function to count valid numbers from current state
    static int countWays(int pos, int cnt, int tight)
    {
        // More than required occurrences
        if (cnt > k)
            return 0;

        // All digits processed
        if (pos == digit.size())
            return (cnt == k) ? 1 : 0;

        // Return already computed result
        if (dp[pos][cnt][tight] != -1)
            return dp[pos][cnt][tight];

        int limit = (tight == 1) ? digit.get(pos) : 9;

        int res = 0;

        // Try all possible digits
        for (int dig = 0; dig <= limit; dig++) {

            int newCnt = cnt;

            if (dig == d)
                newCnt++;

            int newTight
                = (tight == 1 && dig == digit.get(pos)) ? 1
                                                        : 0;

            res += countWays(pos + 1, newCnt, newTight);
        }

        return dp[pos][cnt][tight] = res;
    }

    // Returns count of valid numbers from 0 to x
    static int solve(int x)
    {
        if (x < 0)
            return 0;

        digit.clear();

        // Store digits of x
        if (x == 0)
            digit.add(0);

        while (x > 0) {
            digit.add(x % 10);
            x /= 10;
        }

        Collections.reverse(digit);

        for (int[][] row : dp)
            for (int[] inner : row)
                Arrays.fill(inner, -1);

        return countWays(0, 0, 1);
    }

    // Returns count of valid numbers in range [a, b]
    static int countNumbers(int a, int b, int digitToCount,
                            int requiredCount)
    {
        d = digitToCount;
        k = requiredCount;

        return solve(b) - solve(a - 1);
    }

    // Driver Code
    public static void main(String[] args)
    {
        int a = 100, b = 500;
        int d = 3, k = 1;

        System.out.println(countNumbers(a, b, d, k));
    }
}
Python
from functools import lru_cache


# Python3 program to count numbers in range [a, b]
# where digit d occurs exactly k times
# using Digit DP


digit = []
d, k = 0, 0


@lru_cache(None)
def countWays(pos, cnt, tight):
    if cnt > k:
        return 0
    if pos == len(digit):
        return 1 if cnt == k else 0
    limit = digit[pos] if tight else 9
    res = 0
    for dig in range(limit + 1):
        newCnt = cnt + (1 if dig == d else 0)
        newTight = tight and (dig == digit[pos])
        res += countWays(pos + 1, newCnt, newTight)
    return res


def solve(x):
    if x < 0:
        return 0
    global digit
    digit.clear()
    if x == 0:
        digit.append(0)
    while x > 0:
        digit.append(x % 10)
        x //= 10
    digit.reverse()
    countWays.cache_clear()
    return countWays(0, 0, 1)


def countNumbers(a, b, digitToCount, requiredCount):
    global d, k
    d = digitToCount
    k = requiredCount
    return solve(b) - solve(a - 1)


# Driver Code
if __name__ == "__main__":
    a = 100
    b = 500
    d = 3
    k = 1

    print(countNumbers(a, b, d, k))
C#
using System;
using System.Collections.Generic;

public class GfG {
    static List<int> digit = new List<int>();
    static int[, , ] dp = new int[20, 20, 2];
    static int d, k;

    static int countWays(int pos, int cnt, int tight)
    {
        if (cnt > k)
            return 0;
        if (pos == digit.Count)
            return (cnt == k) ? 1 : 0;
        if (dp[pos, cnt, tight] != -1)
            return dp[pos, cnt, tight];

        int limit = (tight == 1) ? digit[pos] : 9;
        int res = 0;
        for (int dig = 0; dig <= limit; dig++) {
            int newCnt = cnt;
            if (dig == d)
                newCnt++;
            int newTight
                = (tight == 1 && dig == digit[pos]) ? 1 : 0;
            res += countWays(pos + 1, newCnt, newTight);
        }
        return dp[pos, cnt, tight] = res;
    }

    static int solve(int x)
    {
        if (x < 0)
            return 0;
        digit.Clear();
        if (x == 0)
            digit.Add(0);
        while (x > 0) {
            digit.Add(x % 10);
            x /= 10;
        }
        digit.Reverse();
        for (int i = 0; i < 20; i++)
            for (int j = 0; j < 20; j++)
                for (int k = 0; k < 2; k++)
                    dp[i, j, k] = -1;
        return countWays(0, 0, 1);
    }

    static int countNumbers(int a, int b, int digitToCount,
                            int requiredCount)
    {
        d = digitToCount;
        k = requiredCount;
        return solve(b) - solve(a - 1);
    }

    static void Main()
    {
        int a = 100, b = 500;
        int d = 3, k = 1;
        Console.WriteLine(countNumbers(a, b, d, k));
    }
}
JavaScript
let digit = [];
let dp
    = Array.from({length : 20},
                 () => Array.from({length : 20},
                                  () => Array(2).fill(-1)));
let d, k;

function countWays(pos, cnt, tight)
{
    if (cnt > k)
        return 0;
    if (pos === digit.length)
        return cnt === k ? 1 : 0;
    if (dp[pos][cnt][tight] !== -1)
        return dp[pos][cnt][tight];

    let limit = tight ? digit[pos] : 9;
    let res = 0;
    for (let dig = 0; dig <= limit; dig++) {
        let newCnt = cnt + (dig === d ? 1 : 0);
        let newTight
            = tight && (dig === digit[pos]) ? 1 : 0;
        res += countWays(pos + 1, newCnt, newTight);
    }
    return dp[pos][cnt][tight] = res;
}

function solve(x)
{
    if (x < 0)
        return 0;
    digit = [];
    if (x === 0)
        digit.push(0);
    while (x > 0) {
        digit.push(x % 10);
        x = Math.floor(x / 10);
    }
    digit.reverse();
    for (let arr of dp)
        for (let inner of arr)
            inner.fill(-1);
    return countWays(0, 0, 1);
}

function countNumbers(a, b, digitToCount, requiredCount)
{
    d = digitToCount;
    k = requiredCount;
    return solve(b) - solve(a - 1);
}

// Driver Code
// Driver Code
let a = 100, b = 500;
let digitToCount = 3;
let requiredCount = 1;

console.log(
    countNumbers(a, b, digitToCount, requiredCount));

Output
135

Time Complexity: O(d * k * 2 *10)
Auxiliary Space: O(d * k)

Comment