Since the question has changed, this is an answer the new question.
The new question is: Given a range [a, b]
, find number m
such that the total number of digits in range [a, m]
is as close as possible to the number of digits in range [m+1, b]
.
Algorithm explanation
The algorithm is simple: Start with m = (a + b) / 2
, count the digits, then move m
to the right or to the left to adjust.
To count the total number of digits in a range [1, n]
, we first count the number of unit digits (which is n
); then add the number of tens digits (which is n - 9
; then add the number of hundreds digits (which is n - 99
); etc.
To count the total number of digits in a range [a, b]
, we take the difference between the total number of digits in ranges [1, b]
and [1, a-1]
.
Note that the number of digits of a given number n > 1
is given by any of the two expressions math.ceil(math.log10(n))
and len(str(n))
. I used the former in the code below. If you have a phobia of logarithms, you can replace it with the latter; in which case import math
is no longer needed.
Code in python
import math
def count_digits_from_1(n):
power_of_ten = math.ceil(math.log10(n))
total_digits = 0
for i in range(1, power_of_ten+1):
digits_at_pos_i = n - (10**(i-1) - 1)
total_digits += digits_at_pos_i
return total_digits
def count_digits(a, b):
if a > 2:
return count_digits_from_1(b) - count_digits_from_1(a-1)
else:
return count_digits_from_1(b) - (a - 1) # assumes a >= 1
def share_digits(a, b):
total_digits = count_digits(a, b)
m = (a + b) // 2
alices_digits = count_digits(a, m)
bobs_digits = total_digits - alices_digits
direction = 1 if alices_digits < bobs_digits else -1
could_be_more_fair = True
while (could_be_more_fair):
new_m = m + direction
diff = math.ceil(math.log10(new_m))
new_alices_digits = alices_digits + direction * diff
new_bobs_digits = bobs_digits - direction * diff
if abs(alices_digits - bobs_digits) > abs(new_alices_digits - new_bobs_digits):
alices_digits = new_alices_digits
bobs_digits = new_bobs_digits
m = new_m
else:
could_be_more_fair = False
return ((a, m), (m+1, b))
if __name__=='__main__':
for (a, b) in [(1, 200), (8, 10), (9, 10), (8, 11)]:
print('{},{} ---> '.format(a,b), end='')
print(share_digits(a, b))
Output:
1,200 ---> ((1, 118), (119, 200))
8,10 ---> ((8, 9), (10, 10))
9,10 ---> ((9, 9), (10, 10))
8,11 ---> ((8, 10), (11, 11))
Remark: This code uses the assumption 1 <= a <= b
.
Performance analysis
Function count_digits_from1
executes in O(log n)
; its for
loop iterates over the position of the digits to count the number of unit digits, then the number of tens digits, then the number of hundreds digits, etc. There are log10(n)
positions.
The question is: how many iterations will the while
loop in share_digits
have?
If we're lucky, the final value of m
will be very close to the initial value (a+b)//2
, so the number of iterations of this loop might be O(1)
. This remains to be proven.
If the number of iterations of this loop is too high, the algorithm could be improved by getting rid of this loop entirely, and calculating the final value of m
directly. Indeed, replacing m
with m+1
or m-1
changes the difference abs(alices_digits - bobs_digits)
by exactly two times the number of digits of m+1
(or m-1
). Therefore, the final value of m
should be given approximately by:
new_m = m + direction * abs(alices_digits - bobs_digits) / (2 * math.ceil(math.log10(m)))