Skip to main content
Back to problems
#3490
Hard Algorithms

Count beautiful numbers

Dynamic Programming
23.3% acceptance
Feb 25, 2026
51
3
You are given two positive integers, l and r. A positive integer is called beautiful if the product of its digits is divisible by the sum of its digits. Return the count of beautiful numbers between l and r, inclusive.

Solution

Rust
Time O(n)
Space O(n)
LeetCode
solution.rs
impl Solution {
  pub fn beautiful_numbers(l: i32, r: i32) -> i32 {
    (Self::count_upto(r) - Self::count_upto(l - 1)) as i32
  }

  // Digit DP: count beautiful numbers in [1, n].
  // A number is beautiful if product_of_digits % sum_of_digits == 0.
  // Key insight: digit product can only have prime factors {2,3,5,7}.
  // So if product != 0, the sum must be 7-smooth too.
  // State (non-tight, started): (pos, sum, has_zero, c2, c3, c5, c7)
  //   sum: cumulative digit sum (≤81 for ≤9 digits)
  //   has_zero: whether any digit so far is 0 (makes product=0)
  //   c2..c7: prime factor counts in product, capped at the max needed
  //     c2 capped at 6 (max v2 of sum≤81 is v2(64)=6)
  //     c3 capped at 4 (max v3 of sum≤81 is v3(81)=4)
  //     c5 capped at 2 (max v5 of sum≤81 is v5(25)=2)
  //     c7 capped at 2 (max v7 of sum≤81 is v7(49)=2)
  fn count_upto(n: i32) -> i64 {
    if n <= 0 { return 0; }
    let digits: Vec<usize> = {
      let mut v = vec![];
      let mut x = n;
      while x > 0 { v.push((x % 10) as usize); x /= 10; }
      v.reverse();
      v
    };
    // memo[pos][sum][hz][c2][c3][c5][c7] for tight=false, started=true
    // sizes: 9 * 82 * 2 * 7 * 5 * 3 * 3 = 464940
    let mut memo = vec![-1i64; 9 * 82 * 2 * 7 * 5 * 3 * 3];
    Self::dp(0, true, false, 0, false, 0, 0, 0, 0, &digits, &mut memo)
  }

  fn dp(
    pos: usize, tight: bool, started: bool,
    sum: usize, hz: bool,
    c2: usize, c3: usize, c5: usize, c7: usize,
    digits: &[usize], memo: &mut Vec<i64>,
  ) -> i64 {
    if pos == digits.len() {
      return if started && Self::is_beautiful(sum, hz, c2, c3, c5, c7) { 1 } else { 0 };
    }
    let memo_key = if !tight && started {
      let idx = (((((pos * 82 + sum) * 2 + hz as usize) * 7 + c2) * 5 + c3) * 3 + c5) * 3 + c7;
      if memo[idx] >= 0 { return memo[idx]; }
      Some(idx)
    } else {
      None
    };
    let limit = if tight { digits[pos] } else { 9 };
    let mut result = 0i64;
    for d in 0..=limit {
      let new_tight = tight && (d == limit);
      if !(started || d > 0) {
        result += Self::dp(pos + 1, new_tight, false, 0, false, 0, 0, 0, 0, digits, memo);
      } else {
        let new_hz = hz || (d == 0);
        let new_sum = sum + d;
        let (f2, f3, f5, f7) = Self::digit_factors(d);
        result += Self::dp(
          pos + 1, new_tight, true, new_sum, new_hz,
          (c2 + f2).min(6), (c3 + f3).min(4), (c5 + f5).min(2), (c7 + f7).min(2),
          digits, memo,
        );
      }
    }
    if let Some(idx) = memo_key { memo[idx] = result; }
    result
  }

  fn digit_factors(d: usize) -> (usize, usize, usize, usize) {
    match d {
      2 => (1, 0, 0, 0), 3 => (0, 1, 0, 0), 4 => (2, 0, 0, 0),
      5 => (0, 0, 1, 0), 6 => (1, 1, 0, 0), 7 => (0, 0, 0, 1),
      8 => (3, 0, 0, 0), 9 => (0, 2, 0, 0), _ => (0, 0, 0, 0),
    }
  }

  fn is_beautiful(sum: usize, hz: bool, c2: usize, c3: usize, c5: usize, c7: usize) -> bool {
    if sum == 0 { return false; }
    if hz { return true; } // product=0, 0%sum==0
    let mut s = sum;
    let mut n2 = 0usize; while s % 2 == 0 { s /= 2; n2 += 1; }
    let mut n3 = 0usize; while s % 3 == 0 { s /= 3; n3 += 1; }
    let mut n5 = 0usize; while s % 5 == 0 { s /= 5; n5 += 1; }
    let mut n7 = 0usize; while s % 7 == 0 { s /= 7; n7 += 1; }
    s == 1 && c2 >= n2 && c3 >= n3 && c5 >= n5 && c7 >= n7
  }
}