Numbers at most n given digit set
Source: https://leetcode.com/problems/numbers-at-most-n-given-digit-setTags: math, monthly challenge
Problem
Given an array of
digits, you can write numbers using eachdigits[i]as many times as we want.
For example, ifdigits = ['1','3','5'], we may write numbers such as'13','551', and'1351315'.Return the number of positive integers that can be generated that are less than or equal to a given integer
n.
Some questions to ask
- What if
digitsis empty? - Will
digitscontain duplicates? - Does
digitsinclude0? - Is
digitssorted?
Approach
Let n be a x-digit number. Assume you have y digits in your digits set.
Without inspection, you can use all permutations of digits to write numbers up to x-1 digits.
You can write y ** i possible numbers for an i-digit number, so for numbers
up to x-1 digits, you can write (y ** 1) + (y ** 2 ) + ... + (y ** (x - 1)) numbers. This can be computed by the geometric series (first term y, common ratio y). The special case of y=1 (where there is one digit in your digits set), where it reduces to a arithmetic sequence (with difference y=1).
The second half of the problem is to compute how many x-digit numbers can be
generated (from the digits set) that are less than or equal to n.
For each target digit t in n, derive the number of compatible digits from digits, i.e. how many digits s in digits are less than or equal to t.
Then we can proceed as follow, using digits=[1,3,5,7], n=350 as an example:
-
Consider the first digit. We have 2 compatible candidates (
1,3), one of which equals the target digit3. This means we can take1and any permutation of digits for the remaining 2 digits (which is4*4=16) to construct a valid number –1 * (4*4) = 16possible ways.-
i.e.
1xxand we can fill thexwith any of the digits indigits. -
If none of the candidates equal the target digit, we can stop here, because we do not need to inspect the later digits since it is guaranteed that the permutation we calculated here covers the rest of the cases.
-
-
Because the digits match, we need to consider the next digit to filter down the number of matches. Here, we have 3 compatible candidates (
1,3,5), again one of which equals the target digit5. So we take2and any permutation of digits for the remaining 1 digit to construct a valid number –2 * 4 = 8possible ways. -
For the last digit, we do not have a compatible candidate, so we add 0 to the total.
def geometricSeries(*, u_1: int, r: int, n: int) -> int:
"""Compute geometric series up to 'n', given initial value
'u_1' and common ratio 'r'."""
return (u_1 * n) if r == 1 else (u_1 * (u_1 ** n - 1) / (r - 1))
def atMostNGivenDigitSet(digits: List[int], n: int) -> int:
"""Return the number of positive integers that can be generated
from 'digits' that are less than or equal to 'n'."""
numDigits = len(digits)
# Construct target digits
targetDigits = deque()
while n > 0:
n, targetDigit = divmod(n, 10)
targetDigits.appendleft(targetDigit)
numTargetDigits = len(targetDigits)
# First compute number of positive integers that have
# ('numTargetDigits' - 1) digits.
possibilities = geometricSeries(u_1=numDigits,
r=numDigits,
n=numTargetDigits - 1)
# Precompute array 'allPossibilities' such that
# 'allPossibilies[i]' := number of positive integers with
# 'numTargetDigits' - 'i' digits.
allPossibilities = deque()
product = 1
for _ in range(numTargetDigits):
product *= numDigits
allPossibilities.appendleft(product)
for i, targetDigit in enumerate(targetDigits):
# Find candidates from 'digits' for 'i'th digit in target number.
candidates = set(digit for digit in digits if digit <= targetDigit)
numCandidates = len(candidates)
if i + 1 == numTargetDigits:
# Last digit, so take all candidates.
possibilities += numCandidates
elif targetDigit in candidates:
# Only take 'numCandidates - 1' of all possibilities from this
# index onwards; the remaining one depends on the candidates for
# the next digit.
possibilities += ((numCandidates - 1) * allPossibilities[i+1])
else:
# Any combination of digits for later digits will be guaranteed
# to be less than 'n', so take all and return early.
possibilities += (numCandidates * allPossibilities[i+1])
break
return possibilities
Complexity
Let n be a x-digit number. Assume you have y digits in your digits set.
- O(x + xy) time complexity, from:
- iterating through the
x-digit number to construct the target digits; - finding the candidates out of the
y-sized digit set for each of thexdigits in the target number;
- iterating through the
- O(x) space complexity, from:
- storing the target digits;
- storing the
allPossibilitiessuffix product array;