Skip to main content
Back to problems
#3519
Hard Algorithms

Count numbers with non decreasing digits

Math String Dynamic Programming
38.6% acceptance
Feb 25, 2026
53
3
You are given two integers, l and r, represented as strings, and an integer b. Return the count of integers in the inclusive range [l, r] whose digits are in non-decreasing order when represented in base b. Since the answer may be too large, return it modulo 10^9 + 7.

Solution

Rust
Time O(n * m)
Space O(n)
LeetCode
solution.rs
impl Solution {
  pub fn count_numbers(l: String, r: String, b: i32) -> i32 {
    const MOD: i64 = 1_000_000_007;
    let b = b as u32;

    // f(n) = count of integers in [0, n] with non-decreasing base-b digits
    let r_base_b = to_base_b(&r, b);
    let fr = count_nondecreasing(&r_base_b, b);

    let fl = if let Some(l1) = decimal_subtract_one(&l) {
      let l1_base_b = to_base_b(&l1, b);
      count_nondecreasing(&l1_base_b, b)
    } else {
      0 // l = "0" => l-1 is negative, so f(l-1) = 0
    };

    ((fr - fl + MOD) % MOD) as i32
  }
}

/// Convert decimal string to base-b digits (most significant first).
fn to_base_b(decimal_str: &str, b: u32) -> Vec<u32> {
  let mut num: Vec<u32> = decimal_str.bytes().map(|c| (c - b'0') as u32).collect();
  let mut result = Vec::new();

  loop {
    // Check if num == 0
    if num.iter().all(|&d| d == 0) { break; }

    // Long division: num / b, get remainder
    let mut rem = 0u32;
    let mut new_num = Vec::new();
    for &d in &num {
      let cur = rem * 10 + d;
      let q = cur / b;
      rem = cur % b;
      if !new_num.is_empty() || q > 0 {
        new_num.push(q);
      }
    }
    result.push(rem);
    num = if new_num.is_empty() { vec![0] } else { new_num };
  }

  if result.is_empty() { result.push(0); }
  result.reverse();
  result
}

/// f(n): count integers in [0, n] with non-decreasing base-b digits.
/// n is given as base-b digits (most significant first).
fn count_nondecreasing(digits: &[u32], b: u32) -> i64 {
  const MOD: i64 = 1_000_000_007;
  let d = digits.len();
  let b = b as usize;

  // dp[last][tight]: dp[last][tight] = count of valid partial sequences
  // Initial: "haven't started", last=0 (so any d[0]>=0 valid), tight=1
  let mut dp = vec![[0i64; 2]; b];
  dp[0][1] = 1;

  for pos in 0..d {
    let mut new_dp = vec![[0i64; 2]; b];
    let lim = digits[pos] as usize;
    for last in 0..b {
      for tight in 0..2usize {
        let cnt = dp[last][tight];
        if cnt == 0 { continue; }
        let max_d = if tight == 1 { lim } else { b - 1 };
        let start = last; // next digit >= last (non-decreasing)
        for nd in start..=max_d {
          let nt = if tight == 1 && nd == lim { 1 } else { 0 };
          new_dp[nd][nt] = (new_dp[nd][nt] + cnt) % MOD;
        }
      }
    }
    dp = new_dp;
  }

  dp.iter().flat_map(|row| row.iter()).sum::<i64>() % MOD
}

/// Subtract 1 from a decimal string. Returns None if input is "0".
fn decimal_subtract_one(s: &str) -> Option<String> {
  if s == "0" { return None; }
  let mut digits: Vec<u8> = s.bytes().map(|c| c - b'0').collect();
  let n = digits.len();
  let mut i = n - 1;
  loop {
    if digits[i] > 0 {
      digits[i] -= 1;
      break;
    } else {
      digits[i] = 9;
      if i == 0 { break; }
      i -= 1;
    }
  }
  // Remove leading zeros
  let start = digits.iter().position(|&d| d != 0).unwrap_or(n - 1);
  Some(digits[start..].iter().map(|&d| (b'0' + d) as char).collect())
}