Smallest Divisible Digit Product II
HardPrompt
You are given a string num which represents a positive integer, and an integer t.
A number is called zero-free if none of its digits are 0.
Return a string representing the smallest zero-free number greater than or equal to num such that the product of its digits is divisible by t. If no such number exists, return "-1".
Example 1:
Input: num = "1234", t = 256
Output: "1488"
Explanation:
The smallest zero-free number that is greater than 1234 and has the product of its digits divisible by 256 is 1488, with the product of its digits equal to 256.
Example 2:
Input: num = "12355", t = 50
Output: "12355"
Explanation:
12355 is already zero-free and has the product of its digits divisible by 50, with the product of its digits equal to 150.
Example 3:
Input: num = "11111", t = 26
Output: "-1"
Explanation:
No number greater than 11111 has the product of its digits divisible by 26.
Constraints:
2 <= num.length <= 2 * 105numconsists only of digits in the range['0', '9'].numdoes not contain leading zeros.1 <= t <= 1014
Approaches
2 approaches with complexity analysis and trade-offs.
This approach attempts to build the target number digit by digit from left to right using a standard recursive backtracking method, often referred to as 'Digit DP'. The state of the recursion includes the current index, the remaining prime factor requirements, and a 'tight' constraint flag to ensure the generated number is greater than or equal to the input num. Memoization is used to cache the results of subproblems.
Algorithm
- Define a recursive function, for example,
solve(index, p2, p3, p5, p7, isTight). index: The current digit position being filled (from left to right).p2, p3, p5, p7: The remaining counts of prime factors (2, 3, 5, 7) needed.isTight: A boolean flag indicating if we are restricted to digits>= num[index]. If false, we can use any digit from 1-9.- The function explores placing digits
dfrom a valid range at the currentindex. - For each
d, it makes a recursive call forindex + 1with updated parameters. - The base case is when
indexreaches the end of the number. If all factor requirements are met (p_i <= 0), it signifies a valid number. - Memoization is used to store results for states
(index, p2, p3, p5, p7, isTight)to avoid recomputation.
Walkthrough
The core of this method is a recursive function that constructs the result string. The function tries to determine the best digit to place at each position, starting from the most significant digit.
The state of the recursion needs to track:
index: The current position in the number string we are building.p2, p3, p5, p7: The count of prime factors (2, 3, 5, 7) that still need to be covered by the product of the remaining digits.isTight: A boolean flag. Iftrue, the digit at the currentindexcan be fromnum[index]to '9'. If we choose a digitd > num[index], theisTightconstraint is lifted for subsequent positions (isTightbecomesfalse). IfisTightis alreadyfalse, we can choose any digit from '1' to '9'.
The function's goal is to return the smallest possible suffix that can be formed from index onwards, given the factor requirements. If no such suffix exists, it returns a special value indicating failure.
To avoid re-calculating the same state, a multi-dimensional array memo[index][p2][p3][p5][p7][isTight] is used. However, with num.length up to 2 * 10^5, the size of this memoization table becomes impractically large, making the approach infeasible.
// Conceptual code for the recursive approachclass Solution { String num; int[] tFactors; String[][][][][] memo; public String solve(int index, int p2, int p3, int p5, int p7, boolean isTight) { if (p2 <= 0 && p3 <= 0 && p5 <= 0 && p7 <= 0) { // Requirement met, fill rest with '1's StringBuilder ones = new StringBuilder(); for (int i = 0; i < num.length() - index; i++) { ones.append('1'); } return ones.toString(); } if (index == num.length()) { return null; // Failed to meet requirement } if (memo[index][p2][p3][p5][p7][isTight ? 1 : 0] != null) { return memo[index][p2][p3][p5][p7][isTight ? 1 : 0]; } String ans = null; int lowerBound = isTight ? (num.charAt(index) - '0') : 1; for (int d = lowerBound; d <= 9; d++) { if (d == 0) continue; int[] digitFactors = getFactors(d); boolean newIsTight = isTight && (d == lowerBound); String res = solve(index + 1, p2 - digitFactors[0], p3 - digitFactors[1], p5 - digitFactors[2], p7 - digitFactors[3], newIsTight); if (res != null) { ans = d + res; break; // Found the smallest digit `d`, so we can stop } } return memo[index][p2][p3][p5][p7][isTight ? 1 : 0] = ans; } // Helper methods getFactors, etc.}Complexity
Time
O(N * r2 * r3 * r5 * r7 * 10). The recursive function explores each state once due to memoization, and in each state, it iterates through up to 9 possible digits. This is too slow.
Space
O(N * r2 * r3 * r5 * r7), where N is the length of `num` and r_i are the maximum powers of primes in `t`. This is too large for the given constraints.
Trade-offs
Pros
It is a standard and often intuitive approach for problems involving finding numbers with specific properties within a certain range.
The logic correctly finds the lexicographically smallest number by building it from left to right and trying smaller digits first.
Cons
The state space for memoization is prohibitively large due to the length of
num(N) and the range of factor counts (r2, r3, r5, r7). The complexityO(N * r2 * r3 * r5 * r7)is too high for the given constraints.Results in a 'Time Limit Exceeded' or 'Memory Limit Exceeded' error on platforms with strict constraints.
This efficient approach combines precomputation with a constructive search. The core idea is that the optimal suffix of a number (the part that needs to satisfy remaining divisibility conditions) is independent of the prefix. We can precompute the best possible suffixes for all possible factor requirements using dynamic programming. Then, we search for the smallest number greater than or equal to num by trying to modify num at the rightmost possible position, and using our precomputed table to find the optimal suffix.
Algorithm
- Prime Factorization: First, find the prime factorization of
t. Ifthas any prime factor greater than 7, no solution exists. Let the required counts of factors 2, 3, 5, 7 be(r2, r3, r5, r7). - DP Precomputation: Create a DP table
dp[p2][p3][p5][p7]that stores the optimal (shortest, then lexicographically smallest) string of digits whose product yields exactlyp2factors of 2,p3of 3, etc. This table can be populated using Dijkstra's algorithm on the state space of factor counts. - Handle Two Cases: The problem is split into finding the smallest solution of length
n(same asnum) and finding the smallest solution of length> n. - Case 1 (length
n):- Handle any '0's in
numby creating a new, larger, zero-freestartNum. - Iterate from the rightmost digit of
startNum(i = n-1to0). - At each position
i, try to use a digitdgreater thanstartNum[i]. - For the prefix
startNum[0...i-1]d, calculate the remaining factors needed. - Look up the optimal suffix for these factors in the precomputed
dptable. - If the suffix fits in the remaining length, construct the number. The first one found is the smallest of length
n.
- Handle any '0's in
- Case 2 (length
> n):- Find the optimal digit string
sfor the full requirements(r2, r3, r5, r7)from thedptable. - The minimum length of a solution longer than
nism = max(n + 1, s.length()). - Construct the smallest number of length
musing digits fromsand padding with '1's.
- Find the optimal digit string
- Final Result: Compare the results from both cases and return the overall smallest number.
Walkthrough
This method breaks the problem down into a manageable precomputation step and a final construction step.
1. Precomputation using DP on Factors
First, we observe that the product of digits only involves prime factors 2, 3, 5, and 7. If t has other prime factors, we return "-1". We find the required powers of these primes in t, let's say (r2, r3, r5, r7).
The subproblem is: what is the best set of digits to produce a certain combination of prime factors? We define "best" as having the minimum number of digits, and for a tie, being lexicographically smallest when sorted. We can solve this using Dijkstra's algorithm on the state space of factor counts (p2, p3, p5, p7).
- State: A tuple
(p2, p3, p5, p7)representing factor counts. - Distance: A pair
(length, string), wherelengthis the number of digits andstringis the sorted string of digits. - Priority Queue: Stores
(distance, state), ordered by distance.
We initialize distance to (0, "") for state (0,0,0,0) and infinity for all others. The algorithm explores states, adding digits 2-9, and updates distances if a shorter or lexicographically smaller digit string is found. The result is a table, dp[p2][p3][p5][p7], storing the best digit string for each factor requirement.
2. Constructing the Solution
We find the answer by considering two possibilities for the result's length.
Case A: Solution has the same length as num
We want the smallest zero-free number x >= num of length n.
- If
numcontains '0's, we must find a larger number. The smallest zero-free number>= numcan be found by finding the first '0' at indexk, then finding the rightmost non-'9' digit atj < k, incrementing it, and setting digits fromj+1ton-1to '1'. This becomes our new basenum. If no suchjexists, no solution of lengthnis possible. - We check if this new
numis a solution. If not, we iterate fromi = n-1down to0. For eachi, we try digitsdfromnum[i] + 1to9. This forms a prefixnum[0...i-1]d. - We calculate the prime factors provided by this prefix and determine the remaining factors needed.
- We query our
dptable for the best suffix for these remaining factors. To do this, we check alldpstates that satisfy the requirement and take the best one. - If the length of this optimal suffix is less than or equal to the remaining space (
n-1-i), we've found a candidate. We pad with '1's, sort the suffix digits, and form the number. The first such number found is the answer for this case.
Case B: Solution is longer than num
The smallest number longer than num must have the minimum possible length m > n. The minimum number of digits required to satisfy t is given by the length of the optimal string for (r2, r3, r5, r7), let's call it s. So, m = max(n + 1, s.length()). The result is formed by sorting the digits of s along with m - s.length() ones.
Finally, we compare the results from Case A and B and return the smaller one.
// Conceptual code for the efficient approachclass Solution { // DP table and factor arrays private String[][][][] dp; private int[] tFactors; public String smallestDivisibleDigitProductII(String num, long t) { // 1. Factorize t and check for invalid primes // ... // 2. Precompute DP table using Dijkstra precomputeDpTable(); // 3. Find solution for length n String solN = findSolutionOfLengthN(num); // 4. Find solution for length > n String solNPlus = findSolutionOfGreaterLength(num.length()); // 5. Compare and return the best result if (solN == null) return solNPlus; if (solNPlus == null) return solN; if (solN.length() < solNPlus.length()) return solN; if (solN.length() > solNPlus.length()) return solNPlus; return solN.compareTo(solNPlus) < 0 ? solN : solNPlus; } private void precomputeDpTable() { /* ... Dijkstra implementation ... */ } private String findSolutionOfLengthN(String num) { /* ... Case A logic ... */ return null; } private String findSolutionOfGreaterLength(int n) { /* ... Case B logic ... */ return null; }}Complexity
Time
O(F * log(F) * L_max + N), where F is the DP state space size, L_max is max digit string length, and N is `num.length`. The first term is for precomputation, the second for the search.
Space
O(F * L_max), where F is the DP state space size (~500,000) and L_max is the max length of a digit string (~50). This is significant but constant with respect to `num`'s length.
Trade-offs
Pros
Highly efficient for large
numas the main search is linear innum.lengthafter precomputation.Guaranteed to find the optimal solution by systematically exploring modifications from right to left.
The precomputation step is independent of
numandt's magnitude (only depends on prime factor powers), making it reusable for multiple queries.
Cons
The implementation is complex, involving multiple components: prime factorization, a Dijkstra-based DP precomputation, and a constructive search for the final number.
The DP precomputation can be resource-intensive, although it's a one-time cost independent of the input
num's size.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.