math
digit build
]
Leetcode 1067 - Digit Count in Range
Problem statement
https://leetcode.com/problems/digit-count-in-range/
Solution
This problem can be solved using the technique I called digit build: the idea is to construct digit by digit and check number of cases for each group of numbers. First of all notice, that if we know how to solve this problem in range [1, limit], then we can solve original problem. Let us consider example: limit = 142857. Then all numbers smaller than limit can be separated into the following groups:
?*****, where ? is here is smaller than 1. Also we need to avoid numbers, which start with 0, so in this case this group is empty.
1?****, where ? is here can be [0, 1, 2, 3] and * denote any digit.
14?***, where ? here is range [0, 1] and again * is any digit.
142?**, where ? in range [0, 1, 2, 3, 4, 5, 6, 7]
1428?*, where ? in range [0, 1, 2, 3, 4]
14285?, where ? in range [0, 1, 2, 3, 4, 5, 6]
Let us know discuss the steps of our algorithm:
- Transform
limitto string, it will be simpler to work with. Also we add1to it, because this will work for open interval, whereas we want close interval. answill be total number of digits found,kis length of string andqwill be to count number of digitssin prefixes of string.- Now we iterate digit by digit and do the following steps:
- Transform this digit back to integer.
- Update
q: number of digitsdin prefix: we do it in advance, so when we evaluate part3 we will subtract it, see more details later. - If
t == 0, it means that we have current digit equal to0ort == 1andi == 0, we continue, because we have empty group here, like in our example. part1is how many times we have digitdinstead of*in our groups. Ifi == 0, then instead of?we chan havet-1numbers, in opposite case we can havetnumbers. Also, for each ofk-i-1places of*we can put digitdand fill all other places with any of the10options. In total, we have10**(k-i-2)*(k-i-1)*(t-(i==0))options.part2is how many times we have digitdin?place. If bothi == 0andd == 0, then we can not put?to0: we will have number starting with zero in this case. Alsodshould be<= t-1, so if it is not the case we have0options as well. Finally, we have10**(k-i-1)options, becauswe we can put any numbers to*symbol.part3is how many times we have digitdin the resp part of the number. First of all, as I mentioned, we already updatedq, and we did it in advance, so we subtractint(t==d)here. (the reason we updated it in advance to deal with continue case in 3.3). Total number of digits we have here is10**(k-i-1), because we can choose any digits for*, multiplied byt, because we havetoptions to choose?and multiplied by updatedq: number of times we have digitdin our prefix. Note, that we will handle the case?*****for say number231251here correctly, even though we havet-1options for?, but here updatedqwill be equal to0.- Update our
ans, adding all3parts here.
- Finally, we need to deal with numbers with
1, 2, ..., k-1digits as well, for this we can calculate directly number of occurences with direct combinatorial formula.
Complexity
It is O(k), where k is the length of the biggest among low and high. Note also, that here we use exponents of 10, which is in fact not truly O(1), but for small values like we have we can assume that it is O(1). If we imagine, that the problem need to be solved for much bigger numbers and get answer some module M, then this algorithm can be easily adapted to have O(k) complexity.
Code
class Solution:
def digitsCount(self, d, low, high):
def helper(d, limit):
l_str = str(limit + 1)
ans, k, q = 0, len(l_str), 0
for i in range(k):
t = int(l_str[i])
q += (t == d)
if t == 0 or t == 1 and i == 0 : continue
part1 = int(10**(k-i-2)*(k-i-1)*(t-(i==0)))
part2 = int(t-1 >= d) * 10**(k-i-1) if i > 0 or d > 0 else 0
part3 = (q-(t==d)) * t * 10**(k-i-1)
ans += part1 + part2 + part3
return ans + sum(full(d, i) for i in range(1, k))
def full(d, n):
return 10**(n-1)*(9*n+1)//10 if d != 0 else 10**(n-1)*(9*n-9)//10
return helper(d, high) - helper(d, low - 1)