The brute-force approach (testing every integer and counting its divisors) is too slow for large N. A better strategy is to construct x by assigning exponents to small primes: since
x = p1^e1 * p2^e2 * ...,
the number of divisors is (e1+1)*(e2+1)*.... To minimize x for a given divisor-count target, we assign larger exponents to smaller primes (because small primes raised to higher powers grow slower than larger primes).
We implement a constructive search that assigns exponents to the sequence of primes [2,3,5,7,...] with the following rules:
Exponents are non-increasing along the prime list: e1 >= e2 >= e3 .... This ensures we don’t waste large exponents on larger primes.
We search over possible exponent combinations using DFS/backtracking, pruning when the partial x exceeds the current best answer or when the product of (ei+1) already reaches N.
This algorithm enumerates exponent tuples in a greedy, ordered manner and finds the minimal x for the required divisor-count.
Method 1 — Constructive search over prime exponents#
To minimize x for a target divisor-count N, give the largest exponents to the smallest primes. Enforce e1 >= e2 >= ... to avoid duplicate permutations of exponents that only reorder primes.
Precompute a list of small primes (first ~15–20 primes) — enough exponents produce large divisor counts.
Use DFS with parameters idx (current prime index), prev (maximum exponent allowed for current prime), curr_x (current product), and curr_divs (current product of (ei+1)).
At each step, try e from 0 to prev:
Update new_x = curr_x * p[idx]^e (stop if this overflows or exceeds best found).
Update new_divs = curr_divs * (e+1).
If new_divs >= N, update best answer with new_x.
Else recurse to idx+1 with prev = e.
Prune branches where curr_x already ≥ best answer.
from math import prod
from typing import List
classSolution:
defsmallest_with_at_least_n_divs(self, N: int) -> int:
if N <=1: return1 primes = [2,3,5,7,11,13,17,19,23,29,31]
best =10**30defdfs(idx: int, prev: int, curr_x: int, curr_divs: int) ->None:
nonlocal best
if curr_divs >= N:
if curr_x < best: best = curr_x
returnif idx >= len(primes): return p = primes[idx]
x = curr_x
for e in range(0, prev+1):
if e >0:
x *= p
if x >= best: break dfs(idx+1, e, x, curr_divs * (e+1))
dfs(0, 60, 1, 1)
return best
pubstructSolution;
impl Solution {
pubfnsmallest_with_at_least_n_divs(n: u64) -> u128 {
if n <=1 { return1 }
let primes: [u128;11] = [2,3,5,7,11,13,17,19,23,29,31];
letmut best: u128=u128::MAX;
fndfs(idx: usize, prev: usize, curr_x: u128, curr_divs: u64, n: u64, primes: &[u128], best: &mutu128) {
if curr_divs >= n { if curr_x <*best { *best = curr_x }; return }
if idx >= primes.len() { return }
let p = primes[idx];
letmut x = curr_x;
for e in0..=prev {
if e >0 { x = x.saturating_mul(p); if x >=*best { break } }
dfs(idx+1, e, x, curr_divs * (e asu64+1), n, primes, best);
}
}
dfs(0, 60, 1, 1, n, &primes, &mut best);
best
}
}
⏰ Time complexity: highly input-dependent. The DFS enumerates exponent tuples with pruning; in practice for reasonable N (e.g., N up to a few thousand) the search finishes quickly because the exponent-space is small and pruning is effective.
🧺 Space complexity: O(k) recursion depth where k is number of primes used (typically 10–15).