Check if a number can be formed by sum of a number and its reverse

Question:

I want to check if a given number can be formed by another number say b and reverse(b). For example 12 == 6+6, 22 == 11 + 11 and 121 == 29+92. One thing I have figured out is if the number is multiple of 11 or it is an even number less than 20, then it can be formed. I tried to implement this below:

num = 121
if num%11==0:
    print('Yes')
else:
    if num%2==0 and num<20:
        print('Yes')
    else:
        for j in range(11,(int(num)//2)+1):
            if j+int(str(j)[::-1])==num:
                print('Yes')
                break
         

However, if the condition goes into the for loop, it gives TLE. Can any other conditions be given?

Update: If the reversed number has trailing zeroes, it should be removed and then added. For example: 101 == 100+1. I am looking for an optimized form of my code. Or I think I am missing some conditions which can take O(1) time, similar to the condition if num%11==0: print('Yes')

Asked By: SAI SANTOSH CHIRAG

||

Answers:

Can you provide the constraints of the problem?

Here is something you can try:

i = 0
j = num
poss = 0
while(i<=j):
   if(str(i)==str(j)[::-1]):
       poss = 1
       break
   i+=1 
   j-=1
if(poss):
    print("Yes")
else:
    print("No")
Answered By: Ishaan Gupta

You can brute force it like this:

def reverse_digits(n):
    return int(str(n)[::-1])

def sum_of_reversed_numbers(num):
    for i in range(num + 1):
        if i == reverse_digits(num - i):
            return i, num - i
    return None

print("Yes" if sum_of_reversed_numbers(num) else "No")
Answered By: Boris Verkhovskiy

You can do it without str slicing:

def reverse(n):
    r = 0
    while n != 0:
        r = r*10 + int(n%10)
        n = int(n/10)
    return r

def f(n):
    for i in range(n + 1):
        if i + reverse(i) == n:
            return True
    return False

print('Yes' if f(101) else 'No')
#Yes
Answered By: Pablo C

All the previous answers are not really a check. It’s more a brute force try and error.
So let’s do it a little bit smarter.

We start with a number, for example 246808642. we can reduce the problem to the outer 2 place values at the end and start of the number. Let us call this values A and B at the front and Y and Z on the back. the rest, in the middle, is Π. So our number looks now ABΠYZ with A = 2, B = 4, Π = 68086, Y = 4 and Z = 2. (one possible pair of numbers to sum up for this is 123404321). Is A equal to 1, this is only possible for a sum greater 10 (An assumption, but i guess it works, some proof would be nice!).

so if it is a one, we know that the second last number is one greater by the carry over. So we ignore A for the moment and compare B to Z, because they should be the same because both are the result of the addition of the same two numbers. if so, we take the remaining part Π and reduce Y by one (the carry over from the outer addition), and can start again at the top of this chart with Π(Y-1). Only a carry over can make B one bigger than Z, if it’s so, we can replace B by one and start with 1Π(Y-1) at the top. B-1!=Z and B!=Z, we can stop, this isnt possible for such a number which is the sum of a number and its reversed.

If A != 1, we do everything similiar as before but now we use A instead of B. (I cut this here. The answer is long enough.)
Scheme

The code:

import time
def timing(f):
    def wrap(*args, **kwargs):
        time1 = time.time()
        ret = f(*args, **kwargs)
        time2 = time.time()
        print('{:s} function took {:.3f} ms'.format(f.__name__, (time2-time1)*1000.0))

        return ret
    return wrap

@timing
def check(num):
    num = str(num)
    if (int(num) < 20 and int(num)%2 == 0) or (len(num) ==2 and int(num)%11 == 0):
        return print('yes')
    if len(num) <= 2 and int(num)%2 != 0:
        return print('no')
    # get the important place values of the number x
    A = num[0]
    B = num[1]
    remaining = num[2:-2]
    Y = num[-2]
    Z = num[-1]
    # check if A = 1
    if A == '1':
        # A = 1
        # check if B == Z
        if B == Z:
            # so the outest addition matches perfectly and no carry over from inner place values is involved
            # reduce the last digit about one and check again.
            check(remaining + (str(int(Y)-1) if Y != '0' else '9'))
        elif int(B)-1 == int(Z):
            # so the outest addition matches needs a carry over from inner place values to match, so we add to
            # to the remaining part of the number a leading one
            # we modify the last digit of the remaining place values, because the outest had a carry over
            check('1' + remaining + (str(int(Y)-1) if Y != '0' else '9'))
        else:
            print("Not able to formed by a sum of a number and its reversed.")
    else:
        # A != 1
        # check if A == Z
        if A == Z:
            # so the outest addition matches perfectly and no carry over from inner place values is involved
            check(B + remaining + Y)
        elif int(A) - 1 == int(Z):
            # so the outest addition matches needs a carry over from inner place values to match, so we add to
            # to the remaining part of the number a leading one
            # we modify the last digit of the remaining place values, because the outest had a carry over
            check('1' + B + remaining + Y)
        else:
            print("Not able to formed by a sum of a number and its reversed.")

@timing
def loop_check(x):
    for i in range(x + 1):
        if i == int(str(x - i)[::-1]) and not str(x - i).endswith("0"):
            print('yes, by brute force')
            break

loop_check(246808642)
check(246808642)

Result:

yes, by brute force
loop_check function took 29209.069 ms
Yes
check function took 0.000 ms

And another time we see the power of math. Hope this work for you!

Answered By: Doluk

The basic idea of my solution is that you first generate a mapping of digits to the digits that could make them up, so 0 can be made by either 0+0 or 1+9, 2+8 etc. (but in that case there’s a carried 1 you have to keep in mind on the next step). Then you start at the smallest digit, and use that code to check each possible way to form the first digit (this gives you candidates for the first and last digit of the number that sums with its reverse to give you the input number). Then you move on the second digit and try those. This code could be greatly improved by checking both the last and the first digit together, but it’s complicated by the carried 1.

import math

candidates = {}
for a in range(10):
    for b in range(10):
        # a, b, carry
        candidates.setdefault((a + b) % 10, []).append((a, b, (a + b) // 10))


def sum_of_reversed_numbers(num):
    # We reverse the digits because Arabic numerals come from Arabic, which is
    # written right-to-left, whereas English text and arrays are written left-to-right
    digits = [int(d) for d in str(num)[::-1]]

    # result, carry, digit_index
    test_cases = [([None] * len(digits), 0, 0)]

    if len(digits) > 1 and str(num).startswith("1"):
        test_cases.append(([None] * (len(digits) - 1), 0, 0))

    results = []

    while test_cases:
        result, carry, digit_index = test_cases.pop(0)
        if None in result:
            # % 10 because if the current digit is a 0 but we have a carry from
            # the previous digit, it means that the result and its reverse need
            # to actually sum to 9 here so that the +1 carry turns it into a 0
            cur_digit = (digits[digit_index] - carry) % 10
            for a, b, new_carry in candidates[cur_digit]:
                new_result = result[::]
                new_result[digit_index] = a
                new_result[-(digit_index + 1)] = b
                test_cases.append((new_result, new_carry, digit_index + 1))
        else:
            if result[-1] == 0 and num != 0:  # forbid 050 + 050 == 100
                continue
            i = "".join(str(x) for x in result)
            i, j = int(i), int(i[::-1])
            if i + j == num:
                results.append((min(i, j), max(i, j)))

    return results if results else None

We can check the above code by pre-calculating the sums of all numbers from 0 to 10ⁿ and their reverse and storing them in a dict of lists called correct (a list because there’s many ways to form the same number, eg. 11+11 == 02 + 20), which means we have the correct answers for 10ⁿ⁻¹ we can use to check the above function. Btw, if you’re doing this a lot with small numbers, this pre-calculating approach is faster at the expense of memory.

If this code prints nothing it means it works (or your terminal is broken 🙂 )

correct = {}
for num in range(1000000):
    backwards = int(str(num)[::-1])
    components = min(num, backwards), max(num, backwards)
    summed = num + backwards
    correct.setdefault(summed, []).append(components)

for i in range(100000):
    try:
        test = sum_of_reversed_numbers(i)
    except Exception as e:
        raise Exception(i) from e
    if test is None:
        if i in correct:
            print(i, test, correct.get(i))
    elif sorted(test) != sorted(correct[i]):
        print(i, test, correct.get(i))
Answered By: Boris Verkhovskiy

Stole the idea from @Doluk. I was asked this question in a test today. I couldn’t solve it then. With Doluk’s idea and thinking seriously on it below is a decision tree kind for one level of recursion. I might be wrong as I haven’t ran this algorithm.

Let n be the number, we want to check if special

case 1: leading number is not 1

case 1a: no carry over from inner addition

    abcdefgh
    hgfedcba
    
    x      x  => (a+h) < 10
    
    if both ends are same in n, strip both sides by one digit and recurse
    

case 1b: carry over from inner addition

    1    
    abcdefgh
    hgfedcba
    
   (x+1)......(x)  => (a+h+1) < 10

   if left end is 1 greater than right end in n, strip both sides by one digit, add digit 1 on the left and recurse

case 2: leading number is 1

case 2a: no carry over from inner addition

   1      1
    abcdefgh
    hgfedcba
    
   1x      x   => (a+h) >= 10
   
 strip -  if second and last digit are same, strip two digits from left and one from right, from the remaining number minus 1 and recurse.

case 2b: carry over from inner addition

case 2bi: a+h = 9

        11    
         abcdefgh
         hgfedcba

        10......9
        
    strip - two from left and one from right and recurse.
        

case 2bj: a+h >= 10

        11     1
         abcdefgh
         hgfedcba

        1(x+1)......x
        
    strip - two from left and one from right and subtract 1 from number and recurse.
Answered By: Sahil333

In my question, they gave me an array of numbers and they asked me to find numbers which of them are special (Which can be formed by the sum and reverse of that number).
My brute force solution was to iterate from 0 to 1000000 and insert them into the set and last check for each element in the set.

Time Complexity: O(n)

Space Complexity: O(n)

where n is the highest number allowed.

Answered By: Yash Dhanlobhe